# coding=utf-8
"""Opaque Construction."""
from __future__ import division
import re
from honeybee._lockable import lockable
from ._base import _ConstructionBase
from ..material.dictutil import dict_to_material
from ..material._base import _EnergyMaterialOpaqueBase
from ..material.opaque import EnergyMaterial, EnergyMaterialNoMass, \
EnergyMaterialVegetation
from ..reader import parse_idf_string, clean_idf_file_contents
from ..properties.extension import OpaqueConstructionProperties
[docs]
@lockable
class OpaqueConstruction(_ConstructionBase):
"""Opaque energy construction.
Args:
identifier: Text string for a unique Construction 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.
materials: List of materials in the construction (from outside to inside).
All materials must be opaque and a maximum of 10 materials are allowed.
Properties:
* identifier
* display_name
* materials
* layers
* unique_materials
* r_value
* u_value
* u_factor
* r_factor
* is_symmetric
* has_frame
* has_shade
* is_dynamic
* inside_emissivity
* inside_solar_reflectance
* inside_visible_reflectance
* outside_emissivity
* outside_solar_reflectance
* outside_visible_reflectance
* mass_area_density
* area_heat_capacity
* thickness
* inside_material
* outside_material
* user_data
* properties
"""
__slots__ = ()
def __init__(self, identifier, materials):
"""Initialize opaque construction."""
_ConstructionBase.__init__(self, identifier, materials)
self._properties = OpaqueConstructionProperties(self)
[docs]
@classmethod
def from_simple_parameters(
cls, identifier, r_value, roughness='MediumRough',
thermal_absorptance=0.9, solar_absorptance=0.7, visible_absorptance=None
):
"""Create a no-mass OpaqueConstruction from a specification of simple parameters.
The result will have a single EnergyMaterialNoMass layer that is derived
from the input parameters.
Args:
identifier: Text string for a unique construction ID.
Must be <90 characters and not contain any EnergyPlus special
characters. This will be used to identify the object across a model
and in the exported IDF.
r_value: Number for the R-value of the construction [m2-K/W].
roughness: Text describing the relative roughness of the construction.
Must be one of the following: 'VeryRough', 'Rough', 'MediumRough',
'MediumSmooth', 'Smooth', 'VerySmooth'. Default: 'MediumRough'.
thermal_absorptance: A number between 0 and 1 for the fraction of
incident long wavelength radiation that is absorbed by the construction.
Default: 0.9.
solar_absorptance: A number between 0 and 1 for the fraction of incident
solar radiation absorbed by the construction. Default: 0.7.
visible_absorptance: A number between 0 and 1 for the fraction of incident
visible wavelength radiation absorbed by the construction.
Default value is None, which will use the same value as the
solar_absorptance.
"""
mat_layer = EnergyMaterialNoMass(
'{} Material'.format(identifier), r_value, roughness,
thermal_absorptance, solar_absorptance, visible_absorptance
)
return cls(identifier, (mat_layer,))
@property
def materials(self):
"""Get or set the list of materials in the construction (outside to inside).
All materials must be opaque and a maximum of 10 materials are allowed.
"""
return self._materials
@materials.setter
def materials(self, mats):
try:
if not isinstance(mats, tuple):
mats = tuple(mats)
except TypeError:
raise TypeError('Expected list or tuple for construction materials. '
'Got {}'.format(type(mats)))
for i, mat in enumerate(mats):
assert isinstance(mat, _EnergyMaterialOpaqueBase), 'Expected opaque energy' \
' material for construction. Got {}.'.format(type(mat))
if isinstance(mat, EnergyMaterialVegetation):
assert i == 0, 'Vegetation material layer must bet the first ' \
'(exterior) layer in the construction.'
assert len(mats) > 0, 'Construction must possess at least one material.'
assert len(mats) <= 10, 'Opaque Construction cannot have more than 10 materials.'
self._materials = mats
@property
def inside_solar_reflectance(self):
"""The solar reflectance of the inside face of the construction."""
return 1 - self.materials[-1].solar_absorptance
@property
def inside_visible_reflectance(self):
"""The visible reflectance of the inside face of the construction."""
return 1 - self.materials[-1].visible_absorptance
@property
def outside_solar_reflectance(self):
"""The solar reflectance of the outside face of the construction."""
return 1 - self.materials[0].solar_absorptance
@property
def outside_visible_reflectance(self):
"""The visible reflectance of the outside face of the construction."""
return 1 - self.materials[0].visible_absorptance
@property
def mass_area_density(self):
"""The area density of the construction [kg/m2]."""
return sum(tuple(mat.mass_area_density for mat in self.materials))
@property
def area_heat_capacity(self):
"""The heat capacity per unit area of the construction [J/K-m2]."""
return sum(tuple(mat.area_heat_capacity for mat in self.materials))
@property
def thickness(self):
"""Thickness of the construction [m]."""
thickness = 0
for mat in self.materials:
thickness += mat.thickness
return thickness
@property
def inside_material(self):
"""The the inside material layer of the construction.
Useful for checking that an asymmetric construction is correctly assigned.
"""
return self.materials[-1]
@property
def outside_material(self):
"""The the outside material layer of the construction.
Useful for checking that an asymmetric construction is correctly assigned.
"""
return self.materials[0]
[docs]
def temperature_profile(self, outside_temperature=-18, inside_temperature=21,
outside_wind_speed=6.7, solar_irradiance=0,
height=1.0, angle=90.0, pressure=101325):
"""Get a list of temperatures at each material boundary across the construction.
Args:
outside_temperature: The temperature on the outside of the
construction [C]. (Default: -18, consistent with NFRC 100-2010).
inside_temperature: The temperature on the inside of the
construction [C]. (Default: 21, consistent with NFRC 100-2010).
wind_speed: The average outdoor wind speed [m/s]. This affects outdoor
convective heat transfer coefficient. (Default: 6.7 m/s).
solar_irradiance: An optional value for solar irradiance that is incident
on the front (exterior) of the construction [W/m2]. (Default: 0 W/m2).
height: An optional height for the surface in meters. (Default: 1.0 m).
angle: An angle in degrees between 0 and 180.
* 0 = A horizontal surface with the outside boundary on the top.
* 90 = A vertical surface
* 180 = A horizontal surface with the outside boundary on the bottom.
pressure: The average pressure of in Pa. (Default: 101325 Pa for
standard pressure at sea level).
Returns:
A tuple with two elements
- temperatures: A list of temperature values [C].
The first value will always be the outside temperature and the
second will be the exterior surface temperature.
The last value will always be the inside temperature and the second
to last will be the interior surface temperature.
- r_values: A list of R-values for each of the material layers [m2-K/W].
The first value will always be the resistance of the exterior air
and the last value is the resistance of the interior air.
The sum of this list is the R-factor for this construction given
the input parameters.
"""
# reverse the angle if the outside temperature is greater than the inside one
if angle != 90 and outside_temperature > inside_temperature:
angle = abs(180 - angle)
# compute delta temperature from solar irradiance if applicable
heat_gen = None
if solar_irradiance != 0:
heat_gen = self.materials[0].solar_absorptance * solar_irradiance
# use the r-values to get the temperature profile
in_r_init = 1 / self.in_h_simple()
r_values = [1 / self.out_h(outside_wind_speed, outside_temperature + 273.15)] + \
[mat.r_value for mat in self.materials] + [in_r_init]
in_delta_t = (in_r_init / sum(r_values)) * \
(outside_temperature - inside_temperature)
r_values[-1] = 1 / self.in_h(inside_temperature - (in_delta_t / 2) + 273.15,
in_delta_t, height, angle, pressure)
temperatures = self._temperature_profile_from_r_values(
r_values, outside_temperature, inside_temperature, heat_gen)
return temperatures, r_values
[docs]
@classmethod
def from_idf(cls, idf_string, ep_mat_strings):
"""Create an OpaqueConstruction from an EnergyPlus IDF text string.
Args:
idf_string: A text string fully describing an EnergyPlus construction.
ep_mat_strings: A list of text strings for each of the materials in
the construction.
"""
materials_dict = cls._idf_materials_dictionary(ep_mat_strings)
ep_strs = parse_idf_string(idf_string)
try:
materials = [materials_dict[mat.upper()] for mat in ep_strs[1:]]
except KeyError as e:
raise ValueError('Failed to find {} in the input ep_mat_strings.'.format(e))
return cls(ep_strs[0], materials)
[docs]
@classmethod
def from_dict(cls, data):
"""Create a OpaqueConstruction from a dictionary.
Note that the dictionary must be a non-abridged version for this
classmethod to work.
Args:
data: A python dictionary in the following format
.. code-block:: python
{
"type": 'OpaqueConstruction',
"identifier": 'Generic Brick Wall R-10',
"display_name": 'Brick Wall',
"materials": [] # list of unique material objects (from outside to inside)
}
"""
assert data['type'] == 'OpaqueConstruction', \
'Expected OpaqueConstruction. Got {}.'.format(data['type'])
mat_layers = cls._old_schema_materials(data) if 'layers' in data else \
[dict_to_material(mat) for mat in data['materials']]
new_obj = cls(data['identifier'], mat_layers)
if 'display_name' in data and data['display_name'] is not None:
new_obj.display_name = data['display_name']
if 'user_data' in data and data['user_data'] is not None:
new_obj.user_data = data['user_data']
if 'properties' in data and data['properties'] is not None:
new_obj.properties._load_extension_attr_from_dict(data['properties'])
return new_obj
[docs]
@classmethod
def from_dict_abridged(cls, data, materials):
"""Create a OpaqueConstruction from an abridged dictionary.
Args:
data: An OpaqueConstructionAbridged dictionary.
materials: A dictionary with identifiers of materials as keys and Python
material objects as values.
.. code-block:: python
{
"type": 'OpaqueConstructionAbridged',
"identifier": 'Generic Brick Wall R-10',
"display_name": 'Brick Wall',
"materials": [], # list of material identifiers (from outside to inside)
}
"""
assert data['type'] == 'OpaqueConstructionAbridged', \
'Expected OpaqueConstructionAbridged. Got {}.'.format(data['type'])
# handle old schema definition before May 2021 (used layers instead of materials)
mat_key = 'layers' if 'layers' in data else 'materials'
try:
mat_layers = [materials[mat_id] for mat_id in data[mat_key]]
except KeyError as e:
raise ValueError('Failed to find {} in materials.'.format(e))
new_obj = cls(data['identifier'], mat_layers)
if 'display_name' in data and data['display_name'] is not None:
new_obj.display_name = data['display_name']
if 'user_data' in data and data['user_data'] is not None:
new_obj.user_data = data['user_data']
if 'properties' in data and data['properties'] is not None:
new_obj.properties._load_extension_attr_from_dict(data['properties'])
return new_obj
[docs]
def to_idf(self):
"""IDF string representation of construction object.
Note that this method only outputs a single string for the construction and,
to write the full construction into an IDF, the construction's unique_materials
must also be written.
Returns:
construction_idf -- Text string representation of the construction.
.. code-block:: shell
Construction, FLOOR38, ! Material layer names follow:
E5 - ACOUSTIC TILE,
E4 - CEILING AIRSPACE,
C12 - 2 IN HW CONCRETE;
"""
return self._generate_idf_string('opaque', self.identifier, self.materials)
[docs]
def to_radiance_solar_interior(self, specularity=0.0):
"""Honeybee Radiance modifier with the interior solar reflectance."""
return self.materials[-1].to_radiance_solar(specularity)
[docs]
def to_radiance_visible_interior(self, specularity=0.0):
"""Honeybee Radiance modifier with the interior visible reflectance."""
return self.materials[-1].to_radiance_visible(specularity)
[docs]
def to_radiance_solar_exterior(self, specularity=0.0):
"""Honeybee Radiance modifier with the exterior solar reflectance."""
return self.materials[0].to_radiance_solar(specularity)
[docs]
def to_radiance_visible_exterior(self, specularity=0.0):
"""Honeybee Radiance modifier with the exterior visible reflectance."""
return self.materials[0].to_radiance_visible(specularity)
[docs]
def to_dict(self, abridged=False):
"""Opaque construction dictionary representation.
Args:
abridged: Boolean to note whether the full dictionary describing the
object should be returned (False) or just an abridged version (True),
which only specifies the identifiers of material layers. Default: False.
"""
base = {'type': 'OpaqueConstruction'} if not \
abridged else {'type': 'OpaqueConstructionAbridged'}
base['identifier'] = self.identifier
base['materials'] = self.layers if abridged else \
[m.to_dict() for m in self.materials]
if self._display_name is not None:
base['display_name'] = self.display_name
if self._user_data is not None:
base['user_data'] = self.user_data
prop_dict = self.properties.to_dict()
if prop_dict is not None:
base['properties'] = prop_dict
return base
@staticmethod
def _idf_materials_dictionary(ep_mat_strings):
"""Get a dictionary of opaque EnergyMaterial objects from an IDF string list."""
materials_dict = {}
for mat_str in ep_mat_strings:
mat_str = mat_str.strip()
if mat_str.startswith('Material,'):
mat_obj = EnergyMaterial.from_idf(mat_str)
materials_dict[mat_obj.identifier.upper()] = mat_obj
elif mat_str.startswith('Material:NoMass,'):
mat_obj = EnergyMaterialNoMass.from_idf(mat_str)
materials_dict[mat_obj.identifier.upper()] = mat_obj
elif mat_str.startswith('Material:AirGap,'):
mat_obj = EnergyMaterialNoMass.from_idf_air_gap(mat_str)
materials_dict[mat_obj.identifier.upper()] = mat_obj
elif mat_str.startswith('Material:RoofVegetation,'):
mat_obj = EnergyMaterialVegetation.from_idf(mat_str)
materials_dict[mat_obj.identifier.upper()] = mat_obj
return materials_dict
@staticmethod
def _old_schema_materials(data):
"""Get material objects from an old schema definition of OpaqueConstruction.
The schema is from before May 2021 and this method should eventually be removed.
"""
materials = {}
for mat in data['materials']:
materials[mat['identifier']] = dict_to_material(mat)
try:
mat_layers = [materials[mat_id] for mat_id in data['layers']]
except KeyError as e:
raise ValueError(
'Failed to find {} in opaque construction materials.'.format(e))
return mat_layers
def __repr__(self):
"""Represent opaque energy construction."""
return self._generate_idf_string('opaque', self.identifier, self.materials)