# coding=utf-8
"""Parameters with criteria for sizing the heating and cooling system."""
from __future__ import division
from ..reader import parse_idf_string
from ..writer import generate_idf_string
from honeybee.typing import float_positive, valid_ep_string
from ladybug.ddy import DDY
from ladybug.designday import DesignDay
from ladybug.location import Location
[docs]
class SizingParameter(object):
"""Sizing parameters with criteria for sizing the heating and cooling system.
Args:
design_days: An array of Ladybug DesignDay objects that represent the
criteria for which the HVAC systems will be sized. (Default: None).
heating_factor: A number that will get multiplied by the peak heating load
for each zone in the model in order to size the heating system for
the model. Must be greater than 0. (Default: 1.25).
cooling_factor: A number that will get multiplied by the peak cooling load
for each zone in the model in order to size the cooling system for
the model. Must be greater than 0. (Default: 1.15).
efficiency_standard: Text to specify the efficiency standard, which will
automatically set the efficiencies of all HVAC equipment when provided.
Note that providing a standard here will cause the OpenStudio translation
process to perform an additional sizing calculation with EnergyPlus,
which is needed since the default efficiencies of equipment vary depending
on their size. THIS WILL SIGNIFICANTLY INCREASE TRANSLATION TIME.
However, it is often worthwhile when the goal is to match the
HVAC specification with a particular standard. (Default: None).
Choose from the following.
* DOE_Ref_Pre_1980
* DOE_Ref_1980_2004
* ASHRAE_2004
* ASHRAE_2007
* ASHRAE_2010
* ASHRAE_2013
* ASHRAE_2016
* ASHRAE_2019
climate_zone: Text indicating the ASHRAE climate zone to be used with the
efficiency_standard. When unspecified, the climate zone will be
inferred from the design days. This input can be a single
integer (in which case it is interpreted as A) or it can include the
A, B, or C qualifier (eg. 3C).
building_type: Text for the building type to be used in the efficiency_standard.
If the type is not recognized or is None, it will be assumed that the
building is a generic NonResidential. The following have meaning
for the standard.
* NonResidential
* Residential
* MidriseApartment
* HighriseApartment
* LargeOffice
* MediumOffice
* SmallOffice
* Retail
* StripMall
* PrimarySchool
* SecondarySchool
* SmallHotel
* LargeHotel
* Hospital
* Outpatient
* Warehouse
* SuperMarket
* FullServiceRestaurant
* QuickServiceRestaurant
* Laboratory
* Courthouse
bypass_efficiency_sizing: A boolean to indicate whether the efficiency
standard should trigger an sizing run that sets the efficiencies
of all HVAC equipment in the Model (False) or the standard should
only be written into the OSM and the sizing run should be
bypassed (True). Bypassing the sizing run is useful when you only
want to check that the overall HVAC system architecture is correct
and you do not want to wait the extra time that it takes to run the
sizing calculation. (Default: False).
Properties:
* design_days
* heating_factor
* cooling_factor
* efficiency_standard
* climate_zone
* building_type
* bypass_efficiency_sizing
"""
__slots__ = ('_design_days', '_heating_factor', '_cooling_factor',
'_efficiency_standard', '_climate_zone', '_building_type',
'_bypass_efficiency_sizing')
STANDARDS = ('DOE_Ref_Pre_1980', 'DOE_Ref_1980_2004', 'ASHRAE_2004', 'ASHRAE_2007',
'ASHRAE_2010', 'ASHRAE_2013', 'ASHRAE_2016', 'ASHRAE_2019')
CLIMATE_ZONES = (
'0A', '1A', '2A', '3A', '4A', '5A', '6A',
'0B', '1B', '2B', '3B', '4B', '5B', '6B',
'3C', '4C', '5C', '7', '8'
)
def __init__(self, design_days=None, heating_factor=1.25, cooling_factor=1.15,
efficiency_standard=None, climate_zone=None, building_type=None,
bypass_efficiency_sizing=False):
"""Initialize SizingParameter."""
self.design_days = design_days
self.heating_factor = heating_factor
self.cooling_factor = cooling_factor
self.efficiency_standard = efficiency_standard
self.climate_zone = climate_zone
self.building_type = building_type
self.bypass_efficiency_sizing = bypass_efficiency_sizing
@property
def design_days(self):
"""Get or set an array of Ladybug DesignDay objects for sizing criteria."""
return tuple(self._design_days)
@design_days.setter
def design_days(self, value):
if value is not None:
try:
if not isinstance(value, list):
value = list(value)
except TypeError:
raise TypeError('Expected list or tuple for SizingParameter '
'design_days. Got {}'.format(type(value)))
for dday in value:
assert isinstance(dday, DesignDay), 'Expected ladybug DesignDay ' \
'for SizingParameter. Got {}.'.format(type(dday))
self._design_days = value
else:
self._design_days = []
@property
def heating_factor(self):
"""Get or set a number that will get multiplied by the peak heating loads."""
return self._heating_factor
@heating_factor.setter
def heating_factor(self, value):
self._heating_factor = float_positive(value, 'sizing parameter heating factor')
assert self._heating_factor != 0, 'SizingParameter heating factor cannot be 0.'
@property
def cooling_factor(self):
"""Get or set a number that will get multiplied by the peak cooling loads."""
return self._cooling_factor
@cooling_factor.setter
def cooling_factor(self, value):
self._cooling_factor = float_positive(value, 'sizing parameter cooling factor')
assert self._cooling_factor != 0, 'SizingParameter cooling factor cannot be 0.'
@property
def efficiency_standard(self):
"""Get or set text for the efficiency standard.
When specified, this will automatically set the efficiencies of all HVAC
equipment. Note that setting this variable will cause the OpenStudio
translation process to perform an additional sizing calculation with EnergyPlus,
which is needed since the default efficiencies of equipment vary depending
on their size. So THIS WILL SIGNIFICANTLY INCREASE TRANSLATION TIME.
However, it is often worthwhile when the goal is to match the
HVAC specification with a particular standard. Choose from the following.
* DOE_Ref_Pre_1980
* DOE_Ref_1980_2004
* ASHRAE_2004
* ASHRAE_2007
* ASHRAE_2010
* ASHRAE_2013
* ASHRAE_2016
* ASHRAE_2019
"""
return self._efficiency_standard
@efficiency_standard.setter
def efficiency_standard(self, value):
if value:
clean_input = valid_ep_string(value, 'efficiency_standard').lower()
for key in self.STANDARDS:
if key.lower() == clean_input:
value = key
break
else:
raise ValueError(
'Efficiency standard "{}" is not recognized.\nChoose from the '
'following:\n{}'.format(value, self.STANDARDS))
else:
value = None
self._efficiency_standard = value
@property
def climate_zone(self):
"""Get or set text for the climate zone associated with the efficiency standard.
When unspecified, the climate zone will be inferred from the design days.
This input can be a single integer (in which case it is interpreted as A)
or it can include the A, B, or C qualifier (eg. 3C).
"""
return self._climate_zone
@climate_zone.setter
def climate_zone(self, value):
if value:
value = valid_ep_string(value, 'climate_zone').upper()
if len(value) == 1 and value not in ('7', '8'):
value = '{}A'.format(value)
if value not in self.CLIMATE_ZONES:
raise ValueError(
'Efficiency climate zone "{}" is not recognized.\nChoose from the '
'following:\n{}'.format(value, self.CLIMATE_ZONES))
else:
value = None
self._climate_zone = value
@property
def building_type(self):
"""Get or set text for the building type associated with the efficiency standard.
If the type is not recognized or is None, it will be assumed that the
building is a generic NonResidential.
"""
return self._building_type
@building_type.setter
def building_type(self, value):
if value:
value = valid_ep_string(value, 'building_type')
else:
value = None
self._building_type = value
@property
def bypass_efficiency_sizing(self):
"""Get or set a boolean for whether efficiency standard triggers a sizing run."""
return self._bypass_efficiency_sizing
@bypass_efficiency_sizing.setter
def bypass_efficiency_sizing(self, value):
self._bypass_efficiency_sizing = bool(value)
[docs]
def add_design_day(self, design_day):
"""Add a ladybug DesignDay to this object's design_days.
Args:
design_day: A Ladybug DesignDay object to be added to this object.
"""
assert isinstance(design_day, DesignDay), 'Expected ladybug DesignDay for' \
' SizingParameter. Got {}.'.format(type(design_day))
self._design_days.append(design_day)
[docs]
def add_from_ddy(self, ddy_file):
"""Add all design days within a .ddy file to this object.
Args:
ddy_file: The full path to a .ddy file on this machine.
"""
ddy_obj = DDY.from_ddy_file(ddy_file)
for dday in ddy_obj:
self._design_days.append(dday)
[docs]
def add_from_ddy_996_004(self, ddy_file):
"""Add the 99.6% and 0.4% design days within a .ddy file to this object.
99.6% means that this percent of the hours of the year have outside heating
conditions warmer than this design day. 0.4% means that this percent of the
hours of the year have outside cooling conditions cooler than this design day.
Args:
ddy_file: The full path to a .ddy file on this machine.
"""
ddy_obj = DDY.from_ddy_file(ddy_file)
for dday in ddy_obj:
if '99.6%' in dday.name or '.4%' in dday.name:
self._design_days.append(dday)
[docs]
def add_from_ddy_990_010(self, ddy_file):
"""Add the 99.0% and 1.0% design days within a .ddy file to this object.
99.0% means that this percent of the hours of the year have outside heating
conditions warmer than this design day. 1.0% means that this percent of the
hours of the year have outside cooling conditions cooler than this design day.
Args:
ddy_file: The full path to a .ddy file on this machine.
"""
ddy_obj = DDY.from_ddy_file(ddy_file)
for dday in ddy_obj:
if '99%' in dday.name or '1%' in dday.name:
self._design_days.append(dday)
[docs]
def add_from_ddy_keyword(self, ddy_file, keyword):
"""Add DesignDays from a .ddy file using a keyword in the DesignDay name.
Args:
ddy_file: The full path to a .ddy file on this machine.
keyword: String for a keyword, which will be used to select DesignDays
from the .ddy file to add to this object.
"""
ddy_obj = DDY.from_ddy_file(ddy_file)
for dday in ddy_obj:
if keyword in dday.name:
self._design_days.append(dday)
[docs]
def remove_design_day(self, design_day_index):
"""Remove a single DesignDay from this object using an index.
Args:
design_day_index: An integer for the index of the DesignDay to remove.
"""
del self._design_days[design_day_index]
[docs]
def remove_design_day_keyword(self, keyword):
"""Remove DesignDays from this object using a keyword in the DesignDay names.
Args:
keyword: String for a keyword, which will be used to select DesignDays
for deletion from this object.
"""
design_days = []
for dday in self._design_days:
if keyword not in dday.name:
design_days.append(dday)
self._design_days = design_days
[docs]
def remove_all_design_days(self):
"""Remove all DesignDays from this object."""
self._design_days = []
[docs]
def apply_location(self, location):
"""Apply a Ladybug Location object to all of the DesignDays in this object.
This is particularly handy after re-serialization from an IDF since the
IDF does not store the location information in the DesignDay.
Args:
location: A Ladybug Location object.
"""
assert isinstance(location, Location), \
'Expected Ladybug Location. Got {}.'.format(type(Location))
for dday in self._design_days:
dday.location = location
[docs]
@classmethod
def from_idf(cls, design_days=None, sizing_parameter=None, location=None):
"""Create a SizingParameter object from an EnergyPlus IDF text string.
Args:
design_days: An array of of IDF SizingPeriod:DesignDay strings that
represent the criteria for which the HVAC systems will be sized.
If None, no sizing criteria will be included. Default: None.
sizing_parameter: A text string for an EnergyPlus Sizing:Parameters
definition. If None, defaults of 1.25 anf 1.15 will be used.
Default: None.
location: An optional Ladybug Location object, which gets assigned
to the DesignDay objects in order to interpret their SkyConditions.
This object is not used in the export to IDF. If None, the
intersection of the equator with the prime meridian will be used.
Default: None.
"""
# process the input design_days
des_day_objs = None
if design_days is not None:
location = Location() if location is None else location
des_day_objs = [DesignDay.from_idf(dday, location) for dday in design_days]
# process the sizing_parameter
heating_factor = 1.25
cooling_factor = 1.15
if sizing_parameter is not None:
try:
ep_strs = parse_idf_string(sizing_parameter, 'Sizing:Parameters,')
heating_factor = ep_strs[0] if ep_strs[0] != '' else 1.25
cooling_factor = ep_strs[1] if ep_strs[1] != '' else 1.15
except IndexError:
pass # shorter SizingParameters definition
return cls(des_day_objs, heating_factor, cooling_factor)
[docs]
@classmethod
def from_dict(cls, data):
"""Create a SizingParameter object from a dictionary.
Args:
data: A SizingParameter dictionary in following the format.
.. code-block:: python
{
"type": "SizingParameter",
"design_days": [], # Array of Ladybug DesignDay dictionaries
"heating_factor": 1.25,
"cooling_factor": 1.15,
"efficiency_standard": "ASHRAE_2004", # standard for HVAC efficiency
"climate_zone": "5A", # climate zone for HVAC efficiency
"building_type": "LargeOffice", # building type for HVAC efficiency
"bypass_efficiency_sizing": False # bypass the efficiency sizing run
}
"""
assert data['type'] == 'SizingParameter', \
'Expected SizingParameter dictionary. Got {}.'.format(data['type'])
design_days = None
if 'design_days' in data and data['design_days'] is not None:
design_days = [DesignDay.from_dict(dday) for dday in data['design_days']]
heating_factor = data['heating_factor'] if 'heating_factor' in data else 1.25
cooling_factor = data['cooling_factor'] if 'cooling_factor' in data else 1.15
es = data['efficiency_standard'] if 'efficiency_standard' in data else None
cz = data['climate_zone'] if 'climate_zone' in data else None
bt = data['building_type'] if 'building_type' in data else None
bes = data['bypass_efficiency_sizing'] \
if 'bypass_efficiency_sizing' in data else False
return cls(design_days, heating_factor, cooling_factor, es, cz, bt, bes)
[docs]
def to_idf(self):
"""Get an EnergyPlus string representation of the SizingParameters.
Returns:
A tuple with two elements
- design_days: An array of of IDF SizingPeriod:DesignDay strings.
- sizing_parameter: A text string for an EnergyPlus Sizing:Parameters
definition.
.. code-block:: shell
SizingPeriod:DesignDay,
NAS.Jacksonville-Towers.Field Ann Htg 99.6% Condns DB, !- Name
1, !- Month
21, !- Day of Month
WinterDesignDay, !- Day Type
1.3, !- Max Dry-Bulb Temp {C}
0.0, !- Daily Dry-Bulb Temp Range {C}
DefaultMultipliers, !- Dry-Bulb Temp Range Modifier Type
, !- Dry-Bulb Temp Range Modifier Schedule Name
Wetbulb, !- Humidity Condition Type
1.3, !- Wetbulb/Dewpoint at Max Dry-Bulb {C}
, !- Humidity Indicating Day Schedule Name
, !- Humidity Ratio at Maximum Dry-Bulb {kgWater/kgDryAir}
, !- Enthalpy at Maximum Dry-Bulb {J/kg}
, !- Daily Wet-Bulb Temperature Range {deltaC}
101252.0, !- Barometric Pressure {Pa}
4.3, !- Wind Speed {m/s}
320.0, !- Wind Direction {Degrees; N=0, S=180}
No, !- Rain {Yes/No}
No, !- Snow on ground {Yes/No}
No, !- Daylight Savings Time Indicator {Yes/No}
ASHRAEClearSky, !- Solar Model Indicator
, !- Beam Solar Day Schedule Name
, !- Diffuse Solar Day Schedule Name
, !- ASHRAE Clear Sky Beam Optical Depth (taub)
, !- ASHRAE Clear Sky Diffuse Optical Depth (taud)
0.0; !- Clearness (0.0 to 1.2)
"""
# process the design_days
design_days = [dday.to_idf() for dday in self.design_days]
# process the Sizing:Parameters object
values = (self.heating_factor, self.cooling_factor)
comments = ('heating factor', 'cooling factor')
sizing_parameter = generate_idf_string('Sizing:Parameters', values, comments)
return design_days, sizing_parameter
[docs]
def to_dict(self):
"""SizingParameter dictionary representation."""
siz_par = {
'type': 'SizingParameter',
'heating_factor': self.heating_factor,
'cooling_factor': self.cooling_factor
}
if self.efficiency_standard is not None:
siz_par['efficiency_standard'] = self.efficiency_standard
if self.climate_zone is not None:
siz_par['climate_zone'] = self.climate_zone
if self.building_type is not None:
siz_par['building_type'] = self.building_type
if len(self._design_days) != 0:
siz_par['design_days'] = [dday.to_dict(False) for dday in self.design_days]
if self.bypass_efficiency_sizing:
siz_par['bypass_efficiency_sizing'] = self.bypass_efficiency_sizing
return siz_par
[docs]
def to_ddy(self):
"""Get this SizingParameter as a Ladybug DDY object.
This can be written to a .ddy file if so desired.
"""
assert len(self._design_days) != 0, \
'There must be at least one design_day to use SizingParameter.to_ddy.'
return DDY(self._design_days[0].location, self._design_days)
[docs]
def duplicate(self):
"""Get a copy of this object."""
return self.__copy__()
[docs]
def ToString(self):
"""Overwrite .NET ToString."""
return self.__repr__()
def __copy__(self):
return SizingParameter(
[dday.duplicate() for dday in self._design_days],
self.heating_factor, self.cooling_factor, self.efficiency_standard,
self.climate_zone, self.building_type, self.bypass_efficiency_sizing)
def __key(self):
"""A tuple based on the object properties, useful for hashing."""
return tuple(hash(dday) for dday in self._design_days) + \
(self.heating_factor, self.cooling_factor, self.efficiency_standard,
self.climate_zone, self.building_type, self.bypass_efficiency_sizing)
def __hash__(self):
return hash(self.__key())
def __eq__(self, other):
return isinstance(other, SizingParameter) and self.__key() == other.__key()
def __ne__(self, other):
return not self.__eq__(other)
def __len__(self):
return len(self._design_days)
def __getitem__(self, key):
return self._design_days[key]
def __setitem__(self, key, value):
assert isinstance(value, DesignDay), \
'Expected DesignDay type. Got {}'.format(type(value))
self._design_days[key] = value
def __iter__(self):
return iter(self._design_days)
def __contains__(self, item):
return item in self._design_days
def __repr__(self):
return 'SizingParameter: [{} design days] [heating: {}] [cooling: {}]'.format(
len(self._design_days), self.heating_factor, self.cooling_factor)