# coding=utf-8
"""Schedule type definition."""
from __future__ import division
from ..reader import parse_idf_string, clean_idf_file_contents
from ..writer import generate_idf_string
from honeybee.typing import valid_ep_string, valid_string, float_in_range
from honeybee.altnumber import no_limit
from ladybug.datatype import fraction, temperature, temperaturedelta, power, \
angle, speed, distance, uvalue
import re
[docs]
class ScheduleTypeLimit(object):
"""Energy schedule type definition.
Schedule types exist for the sole purpose of validating schedule values against
upper/lower limits and assigning a data type and units to the schedule values.
Args:
identifier: Text string for a unique Schedule Type ID. Must be < 100 characters
and not contain any EnergyPlus special characters. This will be used to
identify the object across a model and in the exported IDF.
lower_limit: An optional number for the lower limit for values in the
schedule. If None or a NoLimit object, there will be no lower limit.
upper_limit: An optional number for the upper limit for values in the
schedule. If None or a NoLimit object, there will be no upper limit.
numeric_type: Either one of two strings: 'Continuous' or 'Discrete'. The
latter means that only integers are accepted as schedule values. (Default:
Continuous).
unit_type: Text for an EnergyPlus unit type, which will be used
to assign units to the values in the schedule. Note that this field
is not used in the actual calculations of EnergyPlus. (Default:
Dimensionless). Choose from the following options:
* Dimensionless
* Temperature
* DeltaTemperature
* PrecipitationRate
* Angle
* ConvectionCoefficient
* ActivityLevel
* Velocity
* Capacity
* Power
* Availability
* Percent
* Control
* Mode
Properties:
* identifier
* display_name
* lower_limit
* upper_limit
* numeric_type
* unit_type
* data_type
* unit
"""
_default_lb_unit_type = {
'Dimensionless': (fraction.Fraction(), 'fraction'),
'Temperature': (temperature.Temperature(), 'C'),
'DeltaTemperature': (temperaturedelta.TemperatureDelta(), 'dC'),
'PrecipitationRate': [distance.Distance(), 'm'],
'Angle': [angle.Angle(), 'degrees'],
'ConvectionCoefficient': [uvalue.ConvectionCoefficient(), 'W/m2-K'],
'ActivityLevel': [power.ActivityLevel(), 'W'],
'Velocity': [speed.Speed(), 'm/s'],
'Capacity': [power.Power(), 'W'],
'Power': [power.Power(), 'W'],
'Availability': [fraction.Fraction(), 'fraction'],
'Percent': [fraction.Fraction(), '%'],
'Control': [fraction.Fraction(), 'fraction'],
'Mode': [fraction.Fraction(), 'fraction']}
UNIT_TYPES = tuple(_default_lb_unit_type.keys())
NUMERIC_TYPES = ('Continuous', 'Discrete')
def __init__(self, identifier, lower_limit=no_limit, upper_limit=no_limit,
numeric_type='Continuous', unit_type='Dimensionless'):
"""Initialize ScheduleTypeLimit."""
# process the identifier and limits
self._identifier = valid_ep_string(identifier, 'schedule type identifier')
self._display_name = None
self._lower_limit = float_in_range(lower_limit) if lower_limit is not \
None and lower_limit != no_limit else no_limit
self._upper_limit = float_in_range(upper_limit) if upper_limit is not \
None and upper_limit != no_limit else no_limit
if self._lower_limit != no_limit and self._upper_limit != no_limit:
assert self._lower_limit <= self._upper_limit, 'ScheduleTypeLimit ' \
'lower_limit must be less than upper_limit. {} > {}.'.format(
self._lower_limit, self._upper_limit)
# process the numeric type
self._numeric_type = numeric_type.capitalize() or 'Continuous'
assert self._numeric_type in self.NUMERIC_TYPES, '"{}" is not an acceptable ' \
'numeric type. Choose from the following:\n{}'.format(
numeric_type, self.NUMERIC_TYPES)
# process the unit type and assign the ladybug data type and unit
if unit_type is None:
self._data_type, self._unit = self._default_lb_unit_type['Dimensionless']
self._unit_type = 'Dimensionless'
else:
clean_input = valid_string(unit_type).lower()
for key in self.UNIT_TYPES:
if key.lower() == clean_input:
unit_type = key
break
else:
raise ValueError(
'unit_type {} is not recognized.\nChoose from the '
'following:\n{}'.format(unit_type, self.UNIT_TYPES))
self._data_type, self._unit = self._default_lb_unit_type[unit_type]
self._unit_type = unit_type
@property
def identifier(self):
"""Get the text string for unique schedule type identifier."""
return self._identifier
@property
def display_name(self):
"""Get or set a string for the object name without any character restrictions.
If not set, this will be equal to the identifier.
"""
if self._display_name is None:
return self._identifier
return self._display_name
@display_name.setter
def display_name(self, value):
if value is not None:
try:
value = str(value)
except UnicodeEncodeError: # Python 2 machine lacking the character set
pass # keep it as unicode
self._display_name = value
@property
def lower_limit(self):
"""Get the lower limit of the schedule type."""
return self._lower_limit
@property
def upper_limit(self):
"""Get the upper limit of the schedule type."""
return self._upper_limit
@property
def numeric_type(self):
"""Text noting whether schedule values are 'Continuous' or 'Discrete'."""
return self._numeric_type
@property
def unit_type(self):
"""Get the text string describing the energyplus unit type."""
return self._unit_type
@property
def data_type(self):
"""Get the Ladybug DataType object corresponding to the energyplus unit type.
This object can be used for creating Ladybug DataCollections, performing unit
conversions of schedule values, etc.
"""
return self._data_type
@property
def unit(self):
"""Get the string describing the units of the schedule values (ie. 'C', 'W')."""
return self._unit
[docs]
@classmethod
def from_idf(cls, idf_string):
"""Create a ScheduleTypeLimit from an IDF string of ScheduleTypeLimits.
Args:
idf_string: A text string describing EnergyPlus ScheduleTypeLimits.
"""
ep_strs = parse_idf_string(idf_string, 'ScheduleTypeLimits,')
ep_fields = [prop if prop != '' else None for prop in ep_strs]
return cls(*ep_fields)
[docs]
@classmethod
def from_dict(cls, data):
"""Create a ScheduleTypeLimit from a dictionary.
Args:
data: ScheduleTypeLimit dictionary following the format below.
.. code-block:: python
{
"type": 'ScheduleTypeLimit',
"identifier": 'Fractional',
"display_name": 'Fractional',
"lower_limit": 0,
"upper_limit": 1,
"numeric_type": Continuous,
"unit_type": "Dimensionless"
}
"""
assert data['type'] == 'ScheduleTypeLimit', \
'Expected ScheduleTypeLimit dictionary. Got {}.'.format(data['type'])
lower_limit = no_limit if 'lower_limit' not in data or \
data['lower_limit'] == no_limit.to_dict() else data['lower_limit']
upper_limit = no_limit if 'upper_limit' not in data or \
data['upper_limit'] == no_limit.to_dict() else data['upper_limit']
numeric_type = data['numeric_type'] if 'numeric_type' in data else 'Continuous'
unit_type = data['unit_type'] if 'unit_type' in data else 'Dimensionless'
new_obj = cls(data['identifier'], lower_limit, upper_limit,
numeric_type, unit_type)
if 'display_name' in data and data['display_name'] is not None:
new_obj.display_name = data['display_name']
return new_obj
[docs]
def to_idf(self):
"""IDF string for the ScheduleTypeLimits of this object.
.. code-block:: shell
ScheduleTypeLimits,
my type limit, !- name
, !- lower limit value
, !- upper limit value
Continuous, !- numeric type
Percent; !- unit type
"""
values = [self.identifier, self.lower_limit, self.upper_limit,
self.numeric_type, self.unit_type]
if values[1] == no_limit:
values[1] = ''
if values[2] == no_limit:
values[2] = ''
comments = ('name', 'lower limit value', 'upper limit value',
'numeric type', 'unit type')
return generate_idf_string('ScheduleTypeLimits', values, comments)
[docs]
def to_dict(self):
"""Shade construction dictionary representation."""
base = {'type': 'ScheduleTypeLimit'}
base['identifier'] = self.identifier
base['lower_limit'] = self.lower_limit if \
isinstance(self.lower_limit, float) else self.lower_limit.to_dict()
base['upper_limit'] = self.upper_limit if \
isinstance(self.upper_limit, float) else self.upper_limit.to_dict()
base['numeric_type'] = self.numeric_type
base['unit_type'] = self.unit_type
if self._display_name is not None:
base['display_name'] = self.display_name
return base
[docs]
def duplicate(self):
"""Get a copy of this object."""
return self.__copy__()
def __copy__(self):
new_obj = ScheduleTypeLimit(
self.identifier, self._lower_limit, self._upper_limit, self._numeric_type,
self._unit_type)
new_obj._display_name = self._display_name
return new_obj
def __key(self):
"""A tuple based on the object properties, useful for hashing."""
return (self.identifier, str(self._lower_limit), str(self._upper_limit),
self._numeric_type, self._unit_type)
def __hash__(self):
return hash(self.__key())
def __eq__(self, other):
return isinstance(other, ScheduleTypeLimit) and self.__key() == other.__key()
def __ne__(self, other):
return not self.__eq__(other)
[docs]
def ToString(self):
"""Overwrite .NET ToString."""
return self.__repr__()
def __repr__(self):
return self.to_idf()