# coding=utf-8
"""EnergyPlus Simulation Run Period."""
from __future__ import division
from ladybug.dt import Date
from ladybug.analysisperiod import AnalysisPeriod
from honeybee.typing import valid_string
from .daylightsaving import DaylightSavingTime
from ..reader import parse_idf_string
from ..writer import generate_idf_string
[docs]
class RunPeriod(object):
"""EnergyPlus Simulation Run Period.
Args:
start_date: A ladybug Date for the start of the run period. (Default: 1 Jan)
end_date: A ladybug Date for the end of the run period. (Default: 31 Dec)
start_day_of_week: Text for the day of the week on which the simulation
starts. Default: 'Sunday'. Choose from the following:
* Sunday
* Monday
* Tuesday
* Wednesday
* Thursday
* Friday
* Saturday
holidays: A list of Ladybug Date objects for the holidays within the
simulation. If None, no holidays are applied. Default: None.
daylight_saving_time: A DaylightSavingTime object to dictate the start and
end dates of daylight saving time. If None, no daylight saving time is
applied to the simulation. Default: None.
Properties:
* start_date
* end_date
* start_day_of_week
* holidays
* daylight_saving_time
* is_leap_year
"""
__slots__ = ('_start_date', '_end_date', '_start_day_of_week', '_holidays',
'_daylight_saving_time')
DAYS_OF_THE_WEEK = (
'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday')
def __init__(self, start_date=Date(1, 1), end_date=Date(12, 31),
start_day_of_week='Sunday', holidays=None, daylight_saving_time=None):
"""Initialize RunPeriod."""
# process the dates
if start_date is not None:
self._check_date(start_date, 'start_date')
self._start_date = start_date
else:
self._start_date = Date(1, 1)
self.end_date = end_date
self.start_day_of_week = start_day_of_week
self.holidays = holidays
self.daylight_saving_time = daylight_saving_time
@property
def start_date(self):
"""Get or set a ladybug Date object for the start of the run period."""
return self._start_date
@start_date.setter
def start_date(self, value):
if value is not None:
self._check_date(value, 'start_date')
self._start_date = value
else:
self._start_date = Date(1, 1)
@property
def end_date(self):
"""Get or set a ladybug Date object for the end of the run period."""
return self._end_date
@end_date.setter
def end_date(self, value):
if value is not None:
self._check_date(value, 'start_date')
self._end_date = value
else:
self._end_date = Date(12, 31)
@property
def start_day_of_week(self):
"""Get or set text for the day of the week on which the simulation starts.
Choose from the following:
* Sunday
* Monday
* Tuesday
* Wednesday
* Thursday
* Friday
* Saturday
"""
return self._start_day_of_week
@start_day_of_week.setter
def start_day_of_week(self, value):
clean_input = valid_string(value).lower()
for key in self.DAYS_OF_THE_WEEK:
if key.lower() == clean_input:
value = key
break
else:
raise ValueError(
'start_day_of_week {} is not recognized.\nChoose from the '
'following:\n{}'.format(value, self.DAYS_OF_THE_WEEK))
self._start_day_of_week = value
@property
def holidays(self):
"""Get or set a list of ladybug Date objects for holidays."""
return self._holidays
@holidays.setter
def holidays(self, value):
if value is not None:
if not isinstance(value, tuple):
value = tuple(value)
for date in value:
assert isinstance(date, Date), 'Expected ladybug Date for ' \
'RunPeriod holiday. Got {}.'.format(type(date))
self._holidays = value
@property
def daylight_saving_time(self):
"""Get or set a DaylightSavingTime object for start and end of daylight savings.
"""
return self._daylight_saving_time
@daylight_saving_time.setter
def daylight_saving_time(self, value):
if value is not None:
assert isinstance(value, DaylightSavingTime), 'Expected DaylightSavingTime' \
' for RunPeriod run_period. Got {}.'.format(type(value))
if value.start_date.leap_year is not self.start_date.leap_year:
leap = self.start_date.leap_year
value._start_date = Date(value._start_date.month,
value._start_date.day, leap)
value._end_date = Date(value._end_date.month,
value._end_date.day, leap)
self._daylight_saving_time = value
@property
def is_leap_year(self):
"""Get or set a boolean noting whether the RunPeriod is for a leap year simulation.
"""
return self.start_date.leap_year
@is_leap_year.setter
def is_leap_year(self, value):
value = bool(value)
self._start_date = Date(self._start_date.month, self._start_date.day, value)
self._end_date = Date(self._end_date.month, self._end_date.day, value)
if self._daylight_saving_time is not None:
st_dt = self._daylight_saving_time._start_date
ed_dt = self._daylight_saving_time._end_date
self._daylight_saving_time._start_date = Date(st_dt.month, st_dt.day, value)
self._daylight_saving_time._end_date = Date(ed_dt.month, ed_dt.day, value)
[docs]
@classmethod
def from_analysis_period(cls, analysis_period=None, start_day_of_week='Sunday',
holidays=None, daylight_saving_time=None):
"""Initialize a RunPeriod object from a ladybug AnalysisPeriod.
Note that the st_hour and end_hour properties of the AnalysisPeriod are
completely ignored when using this classmethod since EnergyPlus cannot start
or end a simulation at an interval less than a day.
Args:
analysis_period: A ladybug AnalysisPeriod object that has the start
and end dates for the simulation. Default: an AnalysisPeriod for the
whole year.
start_day_of_week: Text for the day of the week on which the simulation
starts. Default: 'Sunday'. Choose from the following:
* Sunday
* Monday
* Tuesday
* Wednesday
* Thursday
* Friday
* Saturday
holidays: A list of Ladybug Date objects for the holidays within the
simulation. If None, no holidays are applied. Default: None.
daylight_saving_time: A DaylightSavingTime object to dictate the start and
end dates of daylight saving time. If None, no daylight saving time is
applied to the simulation. Default: None.
"""
assert isinstance(analysis_period, AnalysisPeriod), 'Expected AnalysisPeriod ' \
'for RunPeriod.from_analysis_period. Got {}.'.format(type(analysis_period))
st_date = Date(analysis_period.st_month, analysis_period.st_day,
analysis_period.is_leap_year)
end_date = Date(analysis_period.end_month, analysis_period.end_day,
analysis_period.is_leap_year)
return cls(st_date, end_date, start_day_of_week, holidays, daylight_saving_time)
[docs]
@classmethod
def from_idf(cls, idf_string, holiday_strings=None, daylight_saving_string=None):
"""Create a RunPeriod object from an EnergyPlus IDF text string.
Args:
idf_string: A text string fully describing an EnergyPlus RunPeriod
definition.
holiday_strings: A list of IDF RunPeriodControl:SpecialDays strings
that represent the holidays applied to the simulation.
daylight_saving_string: An IDF RunPeriodControl:DaylightSavingTime string
that notes the start and ends dates of Daylight Savings time.
"""
# check the inputs
ep_strs = parse_idf_string(idf_string, 'RunPeriod,')
# extract the required properties
start_year = int(ep_strs[3]) if ep_strs[3] != '' else 2017
leap_year = True if start_year % 4 == 0 else False
start_date = Date(int(ep_strs[1]), int(ep_strs[2]), leap_year)
end_date = Date(int(ep_strs[4]), int(ep_strs[5]), leap_year)
# extract the optional properties
start_day_of_week = 'Sunday'
try:
start_day_of_week = ep_strs[7] if ep_strs[7] != '' else 'Sunday'
except IndexError:
pass # shorter RunPeriod definition
holidays = None
if holiday_strings is not None:
holidays = []
for hol_str in holiday_strings:
ep_hol_str = parse_idf_string(hol_str, 'RunPeriodControl:SpecialDays,')
hol_vals = ep_hol_str[1].split('/')
holidays.append(Date(int(hol_vals[0]), int(hol_vals[1]), leap_year))
daylight_saving = DaylightSavingTime.from_idf(daylight_saving_string) if \
daylight_saving_string is not None else None
if daylight_saving is not None:
st_dt = daylight_saving._start_date
ed_dt = daylight_saving._end_date
daylight_saving._start_date = Date(st_dt.month, st_dt.day, leap_year)
daylight_saving._end_date = Date(ed_dt.month, ed_dt.day, leap_year)
return cls(start_date, end_date, start_day_of_week, holidays, daylight_saving)
[docs]
@classmethod
def from_dict(cls, data):
"""Create a RunPeriod object from a dictionary.
Args:
data: A RunPeriod dictionary in following the format below.
.. code-block:: python
{
"type": "RunPeriod",
"start_date": (1, 1),
"end_date": (12, 31),
"start_day_of_week": 'Monday',
"holidays": [(1, 1), (7, 4)],
"daylight_saving_time": {}, # DaylightSavingTime dictionary representation
"leap_year": False
}
"""
# check that it is the correct type
assert data['type'] == 'RunPeriod', \
'Expected RunPeriod dictionary. Got {}.'.format(data['type'])
# set a default leap_year value
leap_year = False if 'leap_year' not in data else data['leap_year']
# process the properties
start_date = Date(data['start_date'][0], data['start_date'][1], leap_year) if \
'start_date' in data else Date(1, 1, leap_year)
end_date = Date(data['end_date'][0], data['end_date'][1], leap_year) if \
'end_date' in data else Date(12, 31)
start_day_of_week = data['start_day_of_week'] if \
'start_day_of_week' in data else 'Sunday'
holidays = None
if 'holidays' in data and data['holidays'] is not None:
holidays = tuple(Date(hol[0], hol[1], leap_year) for hol in data['holidays'])
daylight_saving = None
if 'daylight_saving_time' in data and data['daylight_saving_time'] is not None:
daylight_saving = DaylightSavingTime.from_dict(data['daylight_saving_time'])
if daylight_saving is not None:
st_dt = daylight_saving._start_date
ed_dt = daylight_saving._end_date
daylight_saving._start_date = Date(st_dt.month, st_dt.day, leap_year)
daylight_saving._end_date = Date(ed_dt.month, ed_dt.day, leap_year)
return cls(start_date, end_date, start_day_of_week, holidays, daylight_saving)
[docs]
@classmethod
def from_string(cls, run_period_string):
"""Create an RunPeriod object from an RunPeriod string."""
# split the various objects that make us the run period
run_per_objs = run_period_string.split('\n\n')
holidays, dl_saving = None, None
if len(run_per_objs) > 1:
for obj in run_per_objs:
if obj.startswith('RunPeriodControl:DaylightSavingTime'):
dl_saving = obj
elif obj.startswith('RunPeriodControl:SpecialDays'):
holidays = []
lines = obj.split('\n')
for i in range(0, len(lines), 3):
holidays.append('\n'.join(lines[i:i+3]))
return cls.from_idf(run_per_objs[0], holidays, dl_saving)
[docs]
def to_idf(self):
"""Get an EnergyPlus string representation of the RunPeriod.
Returns:
A tuple with three elements
- run_period: An IDF string representation of the RunPeriod object.
- holidays: A list of IDF RunPeriodControl:SpecialDays strings that
represent the holidays applied to the simulation. Will be None
if no holidays are applied to this RunPeriod.
- daylight_saving_time: An IDF RunPeriodControl:DaylightSavingTime string
that notes the start and ends dates of Daylight Savings time. Will be
None if no daylight_saving_time is applied to this RunPeriod.
.. code-block:: shell
RunPeriod, ! Winter Simulation
Winter Simulation, !- Name
12, !- Begin Month
1, !- Begin Day of Month
, !- Begin Year
3, !- End Month
31, !- End Day of Month
, !- End Year
UseWeatherFile, !- Day of Week for Start Day
Yes, !- Use Weather File Holidays and Special Days
Yes, !- Use Weather File Daylight Saving Period
No, !- Apply Weekend Holiday Rule
Yes, !- Use Weather File Rain Indicators
Yes; !- Use Weather File Snow Indicators
"""
year = 2016 if self.is_leap_year else 2017
values = ('CustomRunPeriod', self.start_date.month, self.start_date.day, year,
self.end_date.month, self.end_date.day, year,
self.start_day_of_week, 'No', 'No', 'No', 'Yes', 'Yes')
comments = ('name', 'start month', 'start day', 'start year',
'end month', 'end day', 'end year', 'start day of week',
'use weather file holidays', 'use weather file daylight savings',
'apply weekend holiday', 'use epw rain', 'use epw snow')
run_period = generate_idf_string('RunPeriod', values, comments)
holidays = [self._holiday_to_idf(hol, i) for i, hol in
enumerate(self.holidays)] if self.holidays is not None else None
daylight_saving_time = self.daylight_saving_time.to_idf() if \
self.daylight_saving_time is not None else None
return run_period, holidays, daylight_saving_time
[docs]
def to_dict(self):
"""RunPeriod dictionary representation."""
base = {
'type': 'RunPeriod',
'start_date': (self.start_date.month, self.start_date.day),
'end_date': (self.end_date.month, self.end_date.day),
'start_day_of_week': self.start_day_of_week,
'leap_year': self.is_leap_year
}
if self.holidays is not None:
base['holidays'] = [(hol.month, hol.day) for hol in self.holidays]
if self.daylight_saving_time is not None:
base['daylight_saving_time'] = self.daylight_saving_time.to_dict()
return base
[docs]
def duplicate(self):
"""Get a copy of this object."""
return self.__copy__()
@staticmethod
def _check_date(date, date_name='date'):
assert isinstance(date, Date), 'Expected ladybug Date for ' \
'RunPeriod {}. Got {}.'.format(date_name, type(date))
@staticmethod
def _holiday_to_idf(date, count):
"""Convert a ladybug Date object to an IDF holiday string.
.. code-block:: shell
RunPeriodControl:SpecialDays,
three_day_weekend, !- Name
11/15, !- Start Date
3, !- Duration {days}
Holiday; !- Special Day Type
"""
values = ('Holiday_{}'.format(count), '{}/{}'.format(date.month, date.day))
comments = ('name', 'date')
return generate_idf_string('RunPeriodControl:SpecialDays', values, comments)
[docs]
def ToString(self):
"""Overwrite .NET ToString."""
return self.__repr__()
def __copy__(self):
dst = self.daylight_saving_time.duplicate() if self.daylight_saving_time \
is not None else None
return RunPeriod(self.start_date, self.end_date, self.start_day_of_week,
self.holidays, dst)
def __key(self):
"""A tuple based on the object properties, useful for hashing."""
hol_tup = tuple(hash(hol) for hol in self.holidays) if \
self.holidays is not None else (None,)
return (hash(self.start_date), hash(self.end_date), self.start_day_of_week,
hash(self.daylight_saving_time)) + hol_tup
def __hash__(self):
return hash(self.__key())
def __eq__(self, other):
return isinstance(other, RunPeriod) and self.__key() == other.__key()
def __ne__(self, other):
return not self.__eq__(other)
def __repr__(self):
"""Represent run period."""
run_per, holidays, dl_saving = self.to_idf()
if holidays is not None:
run_per = run_per + '\n\n' + '\n'.join(holidays)
if dl_saving is not None:
run_per = run_per + '\n\n' + dl_saving
return run_per