Source code for archetypal.template.materials.opaque_material

"""archetypal OpaqueMaterial."""

import collections

from eppy.bunch_subclass import EpBunch
from validator_collection import validators

from archetypal.template.materials import GasMaterial
from archetypal.template.materials.material_base import MaterialBase
from archetypal.utils import log


[docs]class OpaqueMaterial(MaterialBase): """Use this component to create a custom opaque material. .. image:: ../images/template/materials-opaque.png """ _ROUGHNESS_TYPES = ( "VeryRough", "Rough", "MediumRough", "MediumSmooth", "Smooth", "VerySmooth", ) __slots__ = ( "_roughness", "_solar_absorptance", "_specific_heat", "_thermal_emittance", "_visible_absorptance", "_moisture_diffusion_resistance", "_conductivity", "_density", "_key" ) def __init__( self, Name, Conductivity, SpecificHeat, SolarAbsorptance=0.7, ThermalEmittance=0.9, VisibleAbsorptance=0.7, Roughness="Rough", Cost=0, Density=1, MoistureDiffusionResistance=50, EmbodiedCarbon=0.45, EmbodiedEnergy=0, TransportCarbon=0, TransportDistance=0, TransportEnergy=0, SubstitutionRatePattern=None, SubstitutionTimestep=20, **kwargs, ): """Initialize an opaque material. Args: Name (str): The name of the material. Conductivity (float): A number representing the conductivity of the material in W/m-K. This is essentially the heat flow in Watts across one meter thick of the material when the temperature difference on either side is 1 Kelvin. Modeling layers with conductivity higher than 5.0 W/(m-K) is not recommended. SpecificHeat (float): A number representing the specific heat capacity of the material in J/kg-K. This is essentially the number of joules needed to raise one kg of the material by 1 degree Kelvin. Only values of specific heat of 100 or larger are allowed. Typical ranges are from 800 to 2000 J/(kg-K). SolarAbsorptance (float): An number between 0 and 1 that represents the absorptance of solar radiation by the material. The default is set to 0.7, which is common for most non-metallic materials. ThermalEmittance (float): An number between 0 and 1 that represents the thermal absorptance of the material. The default is set to 0.9, which is common for most non-metallic materials. For long wavelength radiant exchange, thermal emissivity and thermal emittance are equal to thermal absorptance. VisibleAbsorptance (float): An number between 0 and 1 that represents the absorptance of visible light by the material. The default is set to 0.7, which is common for most non-metallic materials. Roughness (str): A text value that indicated the roughness of your material. This can be either "VeryRough", "Rough", "MediumRough", "MediumSmooth", "Smooth", and "VerySmooth". The default is set to "Rough". Density (float): A number representing the density of the material in kg/m3. This is essentially the mass of one cubic meter of the material. MoistureDiffusionResistance (float): the factor by which the vapor diffusion in the material is impeded, as compared to diffusion in stagnant air [%]. **kwargs: keywords passed to parent constructors. """ super(OpaqueMaterial, self).__init__( Name, Cost=Cost, EmbodiedCarbon=EmbodiedCarbon, EmbodiedEnergy=EmbodiedEnergy, SubstitutionTimestep=SubstitutionTimestep, TransportCarbon=TransportCarbon, TransportDistance=TransportDistance, TransportEnergy=TransportEnergy, SubstitutionRatePattern=SubstitutionRatePattern, **kwargs, ) self.Conductivity = Conductivity self.Density = Density self.Roughness = Roughness self.SolarAbsorptance = SolarAbsorptance self.SpecificHeat = SpecificHeat self.ThermalEmittance = ThermalEmittance self.VisibleAbsorptance = VisibleAbsorptance self.MoistureDiffusionResistance = MoistureDiffusionResistance self._key: str = kwargs.get("_key", "") # TODO: replace when NoMass and AirGap is properly supported @property def Conductivity(self): """Get or set the conductivity of the material [W/m-K].""" return self._conductivity @Conductivity.setter def Conductivity(self, value): self._conductivity = validators.float(value, minimum=0) @property def Density(self): """Get or set the density of the material [J/kg-K].""" return self._density @Density.setter def Density(self, value): self._density = validators.float(value, minimum=0) @property def Roughness(self): """Get or set the roughness of the material. Hint: choices are: "VeryRough", "Rough", "MediumRough", "MediumSmooth", "Smooth", "VerySmooth". """ return self._roughness @Roughness.setter def Roughness(self, value): assert value in self._ROUGHNESS_TYPES, ( f"Invalid value '{value}' for material roughness. Roughness must be one " f"of the following:\n{self._ROUGHNESS_TYPES}" ) self._roughness = value @property def SolarAbsorptance(self): """Get or set the solar absorptance of the material [-].""" return self._solar_absorptance @SolarAbsorptance.setter def SolarAbsorptance(self, value): if value == "" or value is None: value = 0.7 self._solar_absorptance = validators.float( value, minimum=0, maximum=1, allow_empty=True ) @property def SpecificHeat(self): """Get or set the specific heat of the material [J/(kg-K)].""" return self._specific_heat @SpecificHeat.setter def SpecificHeat(self, value): self._specific_heat = validators.float(value, minimum=100) @property def ThermalEmittance(self): """Get or set the thermal emittance of the material [-].""" return self._thermal_emittance @ThermalEmittance.setter def ThermalEmittance(self, value): if value == "" or value is None: value = 0.9 self._thermal_emittance = validators.float( value, minimum=0, maximum=1, allow_empty=True ) @property def VisibleAbsorptance(self): """Get or set the visible absorptance of the material [-].""" return self._visible_absorptance @VisibleAbsorptance.setter def VisibleAbsorptance(self, value): if value == "" or value is None or value is None: value = 0.7 self._visible_absorptance = validators.float( value, minimum=0, maximum=1, allow_empty=True ) @property def MoistureDiffusionResistance(self): """Get or set the vapor resistance factor of the material [%].""" return self._moisture_diffusion_resistance @MoistureDiffusionResistance.setter def MoistureDiffusionResistance(self, value): self._moisture_diffusion_resistance = validators.float(value, minimum=0)
[docs] @classmethod def generic(cls, **kwargs): """Return a generic material based on properties of plaster board. Args: **kwargs: keywords passed to UmiBase constructor. """ return cls( Conductivity=0.16, SpecificHeat=1090, Density=800, Name="GP01 GYPSUM", Roughness="Smooth", SolarAbsorptance=0.7, ThermalEmittance=0.9, VisibleAbsorptance=0.5, DataSource="ASHRAE 90.1-2007", MoistureDiffusionResistance=8.3, **kwargs, )
[docs] def combine(self, other, weights=None, allow_duplicates=False): """Combine two OpaqueMaterial objects. Args: weights (list-like, optional): A list-like object of len 2. If None, the density of the OpaqueMaterial of each objects is used as a weighting factor. other (OpaqueMaterial): The other OpaqueMaterial object the combine with. Returns: OpaqueMaterial: A new combined object made of self + other. """ # Check if other is the same type as self if not isinstance(other, self.__class__): msg = "Cannot combine %s with %s" % ( self.__class__.__name__, other.__class__.__name__, ) raise NotImplementedError(msg) # Check if other is not the same as self if self == other: return self if not weights: log( 'using OpaqueMaterial density as weighting factor in "{}" ' "combine.".format(self.__class__.__name__) ) weights = [self.Density, other.Density] meta = self._get_predecessors_meta(other) new_obj = OpaqueMaterial( **meta, Conductivity=self.float_mean(other, "Conductivity", weights), Roughness=self._str_mean(other, attr="Roughness", append=False), SolarAbsorptance=self.float_mean(other, "SolarAbsorptance", weights), SpecificHeat=self.float_mean(other, "SpecificHeat"), ThermalEmittance=self.float_mean(other, "ThermalEmittance", weights), VisibleAbsorptance=self.float_mean(other, "VisibleAbsorptance", weights), TransportCarbon=self.float_mean(other, "TransportCarbon", weights), TransportDistance=self.float_mean(other, "TransportDistance", weights), TransportEnergy=self.float_mean(other, "TransportEnergy", weights), SubstitutionRatePattern=self.float_mean( other, "SubstitutionRatePattern", weights=None ), SubstitutionTimestep=self.float_mean( other, "SubstitutionTimestep", weights ), Cost=self.float_mean(other, "Cost", weights), Density=self.float_mean(other, "Density", weights), EmbodiedCarbon=self.float_mean(other, "EmbodiedCarbon", weights), EmbodiedEnergy=self.float_mean(other, "EmbodiedEnergy", weights), MoistureDiffusionResistance=self.float_mean( other, "MoistureDiffusionResistance", weights ), ) new_obj.predecessors.update(self.predecessors + other.predecessors) return new_obj
[docs] def to_ref(self): """Return a ref pointer to self.""" pass
[docs] def to_dict(self): """Return OpaqueMaterial dictionary representation.""" self.validate() # Validate object before trying to get json format data_dict = collections.OrderedDict() data_dict["$id"] = str(self.id) data_dict["MoistureDiffusionResistance"] = self.MoistureDiffusionResistance data_dict["Roughness"] = self.Roughness data_dict["SolarAbsorptance"] = self.SolarAbsorptance data_dict["SpecificHeat"] = self.SpecificHeat data_dict["ThermalEmittance"] = self.ThermalEmittance data_dict["VisibleAbsorptance"] = self.VisibleAbsorptance data_dict["Conductivity"] = self.Conductivity data_dict["Cost"] = self.Cost data_dict["Density"] = self.Density data_dict["EmbodiedCarbon"] = self.EmbodiedCarbon data_dict["EmbodiedEnergy"] = self.EmbodiedEnergy data_dict["SubstitutionRatePattern"] = self.SubstitutionRatePattern data_dict["SubstitutionTimestep"] = self.SubstitutionTimestep data_dict["TransportCarbon"] = self.TransportCarbon data_dict["TransportDistance"] = self.TransportDistance data_dict["TransportEnergy"] = self.TransportEnergy data_dict["Category"] = self.Category data_dict["Comments"] = validators.string(self.Comments, allow_empty=True) data_dict["DataSource"] = self.DataSource data_dict["Name"] = self.Name return data_dict
[docs] @classmethod def from_dict(cls, data, **kwargs): """Create an OpaqueMaterial from a dictionary. Args: data (dict): The python dictionary. **kwargs: keywords passed to MaterialBase constructor. .. code-block:: python { "$id": "1", "MoistureDiffusionResistance": 50.0, "Roughness": "Rough", "SolarAbsorptance": 0.7, "SpecificHeat": 920.0, "ThermalEmittance": 0.9, "VisibleAbsorptance": 0.7, "Conductivity": 0.85, "Cost": 0.0, "Density": 2000, "EmbodiedCarbon": 0.45, "EmbodiedEnergy": 0.0, "SubstitutionRatePattern": [ 1.0 ], "SubstitutionTimestep": 20.0, "TransportCarbon": 0.0, "TransportDistance": 0.0, "TransportEnergy": 0.0, "Category": "Uncategorized", "Comments": "", "DataSource": null, "Name": "Concrete" } """ _id = data.pop("$id") return cls(id=_id, **data, **kwargs)
[docs] @classmethod def from_epbunch(cls, epbunch, **kwargs): """Create an OpaqueMaterial from an EpBunch. Note that "Material", "Material:NoMAss" and "Material:AirGap" objects are supported. Hint: (From EnergyPlus Manual): When a user enters such a “no mass” material into EnergyPlus, internally the properties of this layer are converted to approximate the properties of air (density, specific heat, and conductivity) with the thickness adjusted to maintain the user’s desired R-Value. This allowed such layers to be handled internally in the same way as other layers without any additional changes to the code. This solution was deemed accurate enough as air has very little thermal mass and it made the coding of the state space method simpler. For Material:AirGap, a similar strategy is used, with the exception that solar properties (solar and visible absorptance and emittance) are assumed null. Args: epbunch (EpBunch): EP-Construction object **kwargs: """ if epbunch.key.upper() == "MATERIAL": return cls( Conductivity=epbunch.Conductivity, Density=epbunch.Density, Roughness=epbunch.Roughness, SolarAbsorptance=epbunch.Solar_Absorptance, SpecificHeat=epbunch.Specific_Heat, ThermalEmittance=epbunch.Thermal_Absorptance, VisibleAbsorptance=epbunch.Visible_Absorptance, Name=epbunch.Name, **kwargs, ) elif epbunch.key.upper() == "MATERIAL:NOMASS": # Assume properties of air. return cls( Conductivity=0.02436, # W/mK, dry air at 0 °C and 100 kPa Density=1.2754, # dry air at 0 °C and 100 kPa. Roughness=epbunch.Roughness, SolarAbsorptance=epbunch.Solar_Absorptance, SpecificHeat=100.5, # J/kg-K, dry air at 0 °C and 100 kPa ThermalEmittance=epbunch.Thermal_Absorptance, VisibleAbsorptance=epbunch.Visible_Absorptance, Name=epbunch.Name, _key=epbunch.key.upper(), **kwargs, ) elif epbunch.key.upper() == "MATERIAL:AIRGAP": gas_prop = { obj.Name.upper(): obj.mapping() for obj in [GasMaterial(gas_name) for gas_name in GasMaterial._GASTYPES] } for gasname, properties in gas_prop.items(): if gasname.lower() in epbunch.Name.lower(): thickness = properties["Conductivity"] * epbunch.Thermal_Resistance properties.pop("Name") return cls( Name=epbunch.Name, Thickness=thickness, SpecificHeat=100.5, _key=epbunch.key.upper(), **properties, ) else: thickness = ( gas_prop["AIR"]["Conductivity"] * epbunch.Thermal_Resistance ) properties.pop("Name") return cls( Name=epbunch.Name, Thickness=thickness, SpecificHeat=100.5, _key=epbunch.key.upper(), **gas_prop["AIR"], ) else: raise NotImplementedError( "Material '{}' of type '{}' is not yet " "supported. Please contact package " "authors".format(epbunch.Name, epbunch.key) )
[docs] def to_epbunch(self, idf, thickness) -> EpBunch: """Convert self to an EpBunch given an idf model and a thickness. Args: idf (IDF): An IDF model. thickness (float): the thickness of the material. .. code-block:: python MATERIAL, , !- Name , !- Roughness , !- Thickness , !- Conductivity , !- Density , !- Specific Heat 0.9, !- Thermal Absorptance 0.7, !- Solar Absorptance 0.7; !- Visible Absorptance Returns: EpBunch: The EpBunch object added to the idf model. """ if self._key == "MATERIAL:NOMASS": # Special case for Material:NoMass return idf.newidfobject( "MATERIAL:NOMASS", Name=self.Name, Roughness=self.Roughness, Thermal_Resistance=thickness / self.Conductivity, Thermal_Absorptance=self.ThermalEmittance, Solar_Absorptance=self.SolarAbsorptance, Visible_Absorptance=self.VisibleAbsorptance, ) elif self._key == "MATERIAL:AIRGAP": return idf.newidfobject( "MATERIAL:AIRGAP", Name=self.Name, Thermal_Resistance=thickness / self.Conductivity, ) else: return idf.newidfobject( "MATERIAL", Name=self.Name, Roughness=self.Roughness, Thickness=thickness, Conductivity=self.Conductivity, Density=self.Density, Specific_Heat=self.SpecificHeat, Thermal_Absorptance=self.ThermalEmittance, Solar_Absorptance=self.SolarAbsorptance, Visible_Absorptance=self.VisibleAbsorptance, )
[docs] def validate(self): """Validate object and fill in missing values. Hint: Some OpaqueMaterial don't have a default value, therefore an empty string is parsed. This breaks the UmiTemplate Editor, therefore we set a value on these attributes (if necessary) in this validation step. """ if getattr(self, "SolarAbsorptance") == "": setattr(self, "SolarAbsorptance", 0.7) if getattr(self, "ThermalEmittance") == "": setattr(self, "ThermalEmittance", 0.9) if getattr(self, "VisibleAbsorptance") == "": setattr(self, "VisibleAbsorptance", 0.7) return self
[docs] def mapping(self, validate=True): """Get a dict based on the object properties, useful for dict repr. Args: validate (bool): If True, try to validate object before returning the mapping. """ if validate: self.validate() return dict( MoistureDiffusionResistance=self.MoistureDiffusionResistance, Roughness=self.Roughness, SolarAbsorptance=self.SolarAbsorptance, SpecificHeat=self.SpecificHeat, ThermalEmittance=self.ThermalEmittance, VisibleAbsorptance=self.VisibleAbsorptance, Conductivity=self.Conductivity, Cost=self.Cost, Density=self.Density, EmbodiedCarbon=self.EmbodiedCarbon, EmbodiedEnergy=self.EmbodiedEnergy, SubstitutionRatePattern=self.SubstitutionRatePattern, SubstitutionTimestep=self.SubstitutionTimestep, TransportCarbon=self.TransportCarbon, TransportDistance=self.TransportDistance, TransportEnergy=self.TransportEnergy, Category=self.Category, Comments=self.Comments, DataSource=self.DataSource, Name=self.Name, )
[docs] def duplicate(self): """Get copy of self.""" return self.__copy__()
def __add__(self, other): """Overload + to implement self.combine. Args: other (OpaqueMaterial): """ return self.combine(other) def __hash__(self): """Return the hash value of self.""" return hash( ( self.__class__.__name__, getattr(self, "Name", None), ) ) def __eq__(self, other): """Assert self is equivalent to other.""" if not isinstance(other, OpaqueMaterial): return NotImplemented else: return self.__key__() == other.__key__() def __key__(self): """Get a tuple of attributes. Useful for hashing and comparing.""" return ( self.Conductivity, self.SpecificHeat, self.SolarAbsorptance, self.ThermalEmittance, self.VisibleAbsorptance, self.Roughness, self.Cost, self.Density, self.MoistureDiffusionResistance, self.EmbodiedCarbon, self.EmbodiedEnergy, self.TransportCarbon, self.TransportDistance, self.TransportEnergy, self.SubstitutionRatePattern, self.SubstitutionTimestep, ) def __copy__(self): """Create a copy of self.""" new_om = self.__class__(**self.mapping()) return new_om