Source code for fairyfly_therm.material.gas

# coding=utf-8
"""Gas materials."""
from __future__ import division
import xml.etree.ElementTree as ET

from fairyfly._lockable import lockable
from fairyfly.typing import float_positive, float_in_range, tuple_with_length, \
    uuid_from_therm_id

from ._base import _ResourceObjectBase


[docs] @lockable class PureGas(_ResourceObjectBase): """Custom gas gap layer. This object allows you to specify specific values for conductivity, viscosity and specific heat through the following formula: property = A + (B * T) + (C * T ** 2) where: * A, B, and C = regression coefficients for the gas * T = temperature [K] Note that setting properties B and C to 0 will mean the property will be equal to the A coefficient. Args: conductivity_coeff_a: First conductivity coefficient. Or conductivity in [W/m-K] if b and c coefficients are 0. viscosity_coeff_a: First viscosity coefficient. Or viscosity in [kg/m-s] if b and c coefficients are 0. specific_heat_coeff_a: First specific heat coefficient. Or specific heat in [J/kg-K] if b and c coefficients are 0. conductivity_coeff_b: Second conductivity coefficient. Default = 0. viscosity_coeff_b: Second viscosity coefficient. Default = 0. specific_heat_coeff_b: Second specific heat coefficient. Default = 0. conductivity_coeff_c: Third conductivity coefficient. Default = 0. viscosity_coeff_c: Third viscosity coefficient. Default = 0. specific_heat_coeff_c: Third specific heat coefficient. Default = 0. specific_heat_ratio: A number for the the ratio of the specific heat at constant pressure, to the specific heat at constant volume. Default is 1.0 for Air. molecular_weight: Number between 20 and 200 for the mass of 1 mol of the substance in grams. Default is 20.0. identifier: Text string for a unique object ID. Must be a UUID in the format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. If None, a UUID will automatically be generated. (Default: None). Properties: * identifier * display_name * conductivity_coeff_a * viscosity_coeff_a * specific_heat_coeff_a * conductivity_coeff_b * viscosity_coeff_b * specific_heat_coeff_b * conductivity_coeff_c * viscosity_coeff_c * specific_heat_coeff_c * specific_heat_ratio * molecular_weight * protected * user_data Usage: .. code-block:: python co2_mat = PureGas(0.0146, 0.000014, 827.73) co2_mat.display_name = 'CO2' co2_gap.specific_heat_ratio = 1.4 co2_gap.molecular_weight = 44 print(co2_gap) """ __slots__ = ('_conductivity_coeff_a', '_viscosity_coeff_a', '_specific_heat_coeff_a', '_conductivity_coeff_b', '_viscosity_coeff_b', '_specific_heat_coeff_b', '_conductivity_coeff_c', '_viscosity_coeff_c', '_specific_heat_coeff_c', '_specific_heat_ratio', '_molecular_weight') def __init__( self, conductivity_coeff_a, viscosity_coeff_a, specific_heat_coeff_a, conductivity_coeff_b=0, viscosity_coeff_b=0, specific_heat_coeff_b=0, conductivity_coeff_c=0, viscosity_coeff_c=0, specific_heat_coeff_c=0, specific_heat_ratio=1.0, molecular_weight=20.0, identifier=None): """Initialize custom gas energy material.""" _ResourceObjectBase.__init__(self, identifier) self.conductivity_coeff_a = conductivity_coeff_a self.viscosity_coeff_a = viscosity_coeff_a self.specific_heat_coeff_a = specific_heat_coeff_a self.conductivity_coeff_b = conductivity_coeff_b self.viscosity_coeff_b = viscosity_coeff_b self.specific_heat_coeff_b = specific_heat_coeff_b self.conductivity_coeff_c = conductivity_coeff_c self.viscosity_coeff_c = viscosity_coeff_c self.specific_heat_coeff_c = specific_heat_coeff_c self.specific_heat_ratio = specific_heat_ratio self.molecular_weight = molecular_weight @property def conductivity_coeff_a(self): """Get or set the first conductivity coefficient.""" return self._conductivity_coeff_a @conductivity_coeff_a.setter def conductivity_coeff_a(self, coeff): self._conductivity_coeff_a = float(coeff) @property def viscosity_coeff_a(self): """Get or set the first viscosity coefficient.""" return self._viscosity_coeff_a @viscosity_coeff_a.setter def viscosity_coeff_a(self, coeff): self._viscosity_coeff_a = float_positive(coeff) @property def specific_heat_coeff_a(self): """Get or set the first specific heat coefficient.""" return self._specific_heat_coeff_a @specific_heat_coeff_a.setter def specific_heat_coeff_a(self, coeff): self._specific_heat_coeff_a = float_positive(coeff) @property def conductivity_coeff_b(self): """Get or set the second conductivity coefficient.""" return self._conductivity_coeff_b @conductivity_coeff_b.setter def conductivity_coeff_b(self, coeff): self._conductivity_coeff_b = float(coeff) @property def viscosity_coeff_b(self): """Get or set the second viscosity coefficient.""" return self._viscosity_coeff_b @viscosity_coeff_b.setter def viscosity_coeff_b(self, coeff): self._viscosity_coeff_b = float(coeff) @property def specific_heat_coeff_b(self): """Get or set the second specific heat coefficient.""" return self._specific_heat_coeff_b @specific_heat_coeff_b.setter def specific_heat_coeff_b(self, coeff): self._specific_heat_coeff_b = float(coeff) @property def conductivity_coeff_c(self): """Get or set the third conductivity coefficient.""" return self._conductivity_coeff_c @conductivity_coeff_c.setter def conductivity_coeff_c(self, coeff): self._conductivity_coeff_c = float(coeff) @property def viscosity_coeff_c(self): """Get or set the third viscosity coefficient.""" return self._viscosity_coeff_c @viscosity_coeff_c.setter def viscosity_coeff_c(self, coeff): self._viscosity_coeff_c = float(coeff) @property def specific_heat_coeff_c(self): """Get or set the third specific heat coefficient.""" return self._specific_heat_coeff_c @specific_heat_coeff_c.setter def specific_heat_coeff_c(self, coeff): self._specific_heat_coeff_c = float(coeff) @property def specific_heat_ratio(self): """Get or set the specific heat ratio.""" return self._specific_heat_ratio @specific_heat_ratio.setter def specific_heat_ratio(self, number): number = float(number) assert 1 <= number, 'Input specific_heat_ratio ({}) must be > 1.'.format(number) self._specific_heat_ratio = number @property def molecular_weight(self): """Get or set the molecular weight.""" return self._molecular_weight @molecular_weight.setter def molecular_weight(self, number): self._molecular_weight = float_in_range( number, 2.0, 300.0, 'gas material molecular weight') @property def conductivity(self): """Conductivity of the gas in the absence of convection at 0C [W/m-K].""" return self.conductivity_at_temperature(273.15) @property def viscosity(self): """Viscosity of the gas at 0C [kg/m-s].""" return self.viscosity_at_temperature(273.15) @property def specific_heat(self): """Specific heat of the gas at 0C [J/kg-K].""" return self.specific_heat_at_temperature(273.15) @property def density(self): """Density of the gas at 0C and sea-level pressure [J/kg-K].""" return self.density_at_temperature(273.15) @property def prandtl(self): """Prandtl number of the gas at 0C.""" return self.prandtl_at_temperature(273.15)
[docs] def conductivity_at_temperature(self, t_kelvin): """Get the conductivity of the gas [W/m-K] at a given Kelvin temperature.""" return self.conductivity_coeff_a + self.conductivity_coeff_b * t_kelvin + \ self.conductivity_coeff_c * t_kelvin ** 2
[docs] def viscosity_at_temperature(self, t_kelvin): """Get the viscosity of the gas [kg/m-s] at a given Kelvin temperature.""" return self.viscosity_coeff_a + self.viscosity_coeff_b * t_kelvin + \ self.viscosity_coeff_c * t_kelvin ** 2
[docs] def specific_heat_at_temperature(self, t_kelvin): """Get the specific heat of the gas [J/kg-K] at a given Kelvin temperature.""" return self.specific_heat_coeff_a + self.specific_heat_coeff_b * t_kelvin + \ self.specific_heat_coeff_c * t_kelvin ** 2
[docs] def density_at_temperature(self, t_kelvin, pressure=101325): """Get the density of the gas [kg/m3] at a given temperature and pressure. This method uses the ideal gas law to estimate the density. Args: t_kelvin: The average temperature of the gas cavity in Kelvin. pressure: The average pressure of the gas cavity in Pa. Default is 101325 Pa for standard pressure at sea level. """ return (pressure * self.molecular_weight * 0.001) / (8.314 * t_kelvin)
[docs] def prandtl_at_temperature(self, t_kelvin): """Get the Prandtl number of the gas at a given Kelvin temperature.""" return self.viscosity_at_temperature(t_kelvin) * \ self.specific_heat_at_temperature(t_kelvin) / \ self.conductivity_at_temperature(t_kelvin)
[docs] @classmethod def from_therm_xml(cls, xml_element): """Create PureGas from an XML element of a THERM PureGas material. Args: xml_element: An XML element of a THERM PureGas material. """ # get the identifier, molecular weight and specific heat ratio xml_uuid = xml_element.find('UUID') identifier = xml_uuid.text if len(identifier) == 31: identifier = uuid_from_therm_id(identifier) xml_prop = xml_element.find('Properties') xml_mw = xml_prop.find('MolecularWeight') molecular_weight = xml_mw.text xml_shr = xml_prop.find('SpecificHeatRatio') specific_heat_ratio = xml_shr.text # extract the conductivity curve xml_cond = xml_prop.find('Conductivity') xml_c_a = xml_cond.find('A') conductivity_coeff_a = xml_c_a.text xml_c_b = xml_cond.find('B') conductivity_coeff_b = xml_c_b.text xml_c_c = xml_cond.find('C') conductivity_coeff_c = xml_c_c.text # extract the viscosity curve xml_vis = xml_prop.find('Viscosity') xml_v_a = xml_vis.find('A') viscosity_coeff_a = xml_v_a.text xml_v_b = xml_vis.find('B') viscosity_coeff_b = xml_v_b.text xml_v_c = xml_vis.find('C') viscosity_coeff_c = xml_v_c.text # extract the specific heat curve xml_sh = xml_prop.find('SpecificHeat') xml_sh_a = xml_sh.find('A') specific_heat_coeff_a = xml_sh_a.text xml_sh_b = xml_sh.find('B') specific_heat_coeff_b = xml_sh_b.text xml_sh_c = xml_sh.find('C') specific_heat_coeff_c = xml_sh_c.text # create the PureGas material mat = PureGas( conductivity_coeff_a, viscosity_coeff_a, specific_heat_coeff_a, conductivity_coeff_b, viscosity_coeff_b, specific_heat_coeff_b, conductivity_coeff_c, viscosity_coeff_c, specific_heat_coeff_c, specific_heat_ratio, molecular_weight, identifier=identifier) # assign the name if it is specified xml_name = xml_element.find('Name') if xml_name is not None: mat.display_name = xml_name.text xml_protect = xml_element.find('Protected') if xml_protect is not None: mat.protected = True if xml_protect.text == 'true' else False return mat
[docs] @classmethod def from_therm_xml_str(cls, xml_str): """Create a PureGas from an XML text string of a THERM PureGas. Args: xml_str: An XML text string of a THERM PureGas. """ root = ET.fromstring(xml_str) return cls.from_therm_xml(root)
[docs] @classmethod def from_dict(cls, data): """Create a PureGas from a dictionary. Args: data: A python dictionary in the following format .. code-block:: python { "type": 'PureGas', "identifier": '7b4a5a47-ebec-4d95-b028-a78485130c34', "display_name": 'CO2' "conductivity_coeff_a": 0.0146, "viscosity_coeff_a": 0.000014, "specific_heat_coeff_a": 827.73, "specific_heat_ratio": 1.4 "molecular_weight": 44 } """ assert data['type'] == 'PureGas', \ 'Expected PureGas. Got {}.'.format(data['type']) con_b = 0 if 'conductivity_coeff_b' not in data else data['conductivity_coeff_b'] vis_b = 0 if 'viscosity_coeff_b' not in data else data['viscosity_coeff_b'] sph_b = 0 if 'specific_heat_coeff_b' not in data \ else data['specific_heat_coeff_b'] con_c = 0 if 'conductivity_coeff_c' not in data else data['conductivity_coeff_c'] vis_c = 0 if 'viscosity_coeff_c' not in data else data['viscosity_coeff_c'] sph_c = 0 if 'specific_heat_coeff_c' not in data \ else data['specific_heat_coeff_c'] sphr = 1.0 if 'specific_heat_ratio' not in data else data['specific_heat_ratio'] mw = 20.0 if 'molecular_weight' not in data else data['molecular_weight'] new_obj = cls( data['conductivity_coeff_a'], data['viscosity_coeff_a'], data['specific_heat_coeff_a'], con_b, vis_b, sph_b, con_c, vis_c, sph_c, sphr, mw, identifier=data['identifier']) if 'display_name' in data and data['display_name'] is not None: new_obj.display_name = data['display_name'] if 'protected' in data and data['protected'] is not None: new_obj.protected = data['protected'] if 'user_data' in data and data['user_data'] is not None: new_obj.user_data = data['user_data'] return new_obj
[docs] def to_therm_xml(self, gases_element=None): """Get an THERM XML element of the gas. Args: gases_element: An optional XML Element for the Gases to which the generated objects will be added. If None, a new XML Element will be generated. .. code-block:: xml <PureGas> <UUID>8d33196f-f052-46e6-8353-bccb9a779f9c</UUID> <Name>Air</Name> <Protected>true</Protected> <Properties> <MolecularWeight>28.97</MolecularWeight> <SpecificHeatRatio>1.4</SpecificHeatRatio> <Conductivity> <A>0.002873</A> <B>7.76e-05</B> <C>0</C> </Conductivity> <Viscosity> <A>3.723e-06</A> <B>4.94e-08</B> <C>0</C> </Viscosity> <SpecificHeat> <A>1002.737</A> <B>0.012324</B> <C>0</C> </SpecificHeat> </Properties> </PureGas> """ # create a new Materials element if one is not specified if gases_element is not None: xml_mat = ET.SubElement(gases_element, 'PureGas') else: xml_mat = ET.Element('PureGas') # add all of the required basic attributes xml_id = ET.SubElement(xml_mat, 'UUID') xml_id.text = self.identifier xml_name = ET.SubElement(xml_mat, 'Name') xml_name.text = self.display_name xml_protect = ET.SubElement(xml_mat, 'Protected') xml_protect.text = 'true' if self.protected else 'false' xml_prop = ET.SubElement(xml_mat, 'Properties') # molecular weight and specific heat ratio xml_mw = ET.SubElement(xml_prop, 'MolecularWeight') xml_mw.text = str(self.molecular_weight) xml_shr = ET.SubElement(xml_prop, 'SpecificHeatRatio') xml_shr.text = str(self.specific_heat_ratio) # add the conductivity curve xml_cond = ET.SubElement(xml_prop, 'Conductivity') xml_cond_a = ET.SubElement(xml_cond, 'A') xml_cond_a.text = str(self.conductivity_coeff_a) xml_cond_b = ET.SubElement(xml_cond, 'B') xml_cond_b.text = str(self.conductivity_coeff_b) xml_cond_c = ET.SubElement(xml_cond, 'C') xml_cond_c.text = str(self.conductivity_coeff_c) # add the viscosity curve xml_vis = ET.SubElement(xml_prop, 'Viscosity') xml_vis_a = ET.SubElement(xml_vis, 'A') xml_vis_a.text = str(self.viscosity_coeff_a) xml_vis_b = ET.SubElement(xml_vis, 'B') xml_vis_b.text = str(self.viscosity_coeff_b) xml_vis_c = ET.SubElement(xml_vis, 'C') xml_vis_c.text = str(self.viscosity_coeff_c) # add the specific heat curve xml_sh = ET.SubElement(xml_prop, 'SpecificHeat') xml_sh_a = ET.SubElement(xml_sh, 'A') xml_sh_a.text = str(self.specific_heat_coeff_a) xml_sh_b = ET.SubElement(xml_sh, 'B') xml_sh_b.text = str(self.specific_heat_coeff_b) xml_sh_c = ET.SubElement(xml_sh, 'C') xml_sh_c.text = str(self.specific_heat_coeff_c) return xml_mat
[docs] def to_therm_xml_str(self): """Get an THERM XML string of the gas.""" xml_root = self.to_therm_xml() try: # try to indent the XML to make it read-able ET.indent(xml_root) return ET.tostring(xml_root, encoding='unicode') except AttributeError: # we are in Python 2 and no indent is available return ET.tostring(xml_root)
[docs] def to_dict(self): """PureGas dictionary representation.""" base = { 'type': 'PureGas', 'identifier': self.identifier, 'conductivity_coeff_a': self.conductivity_coeff_a, 'viscosity_coeff_a': self.viscosity_coeff_a, 'specific_heat_coeff_a': self.specific_heat_coeff_a, 'conductivity_coeff_b': self.conductivity_coeff_b, 'viscosity_coeff_b': self.viscosity_coeff_b, 'specific_heat_coeff_b': self.specific_heat_coeff_b, 'conductivity_coeff_c': self.conductivity_coeff_c, 'viscosity_coeff_c': self.viscosity_coeff_c, 'specific_heat_coeff_c': self.specific_heat_coeff_c, 'specific_heat_ratio': self.specific_heat_ratio, 'molecular_weight': self.molecular_weight } if self._display_name is not None: base['display_name'] = self.display_name base['protected'] = self._protected if self._user_data is not None: base['user_data'] = self.user_data return base
def __key(self): """A tuple based on the object properties, useful for hashing.""" return (self.identifier, self.conductivity_coeff_a, self.viscosity_coeff_a, self.specific_heat_coeff_a, self.conductivity_coeff_b, self.viscosity_coeff_b, self.specific_heat_coeff_b, self.conductivity_coeff_c, self.viscosity_coeff_c, self.specific_heat_coeff_c, self.specific_heat_ratio, self.molecular_weight) def __hash__(self): return hash(self.__key()) def __eq__(self, other): return isinstance(other, PureGas) and \ self.__key() == other.__key() def __ne__(self, other): return not self.__eq__(other) def __repr__(self): return 'THERM Pure Gas: {}'.format(self.display_name) def __copy__(self): new_obj = PureGas( self.conductivity_coeff_a, self.viscosity_coeff_a, self.specific_heat_coeff_a, self.conductivity_coeff_b, self.viscosity_coeff_b, self.specific_heat_coeff_b, self.conductivity_coeff_c, self.viscosity_coeff_c, self.specific_heat_coeff_c, self.specific_heat_ratio, self.molecular_weight, identifier=self.identifier) new_obj._display_name = self._display_name new_obj._protected = self._protected new_obj._user_data = None if self._user_data is None else self._user_data.copy() return new_obj
[docs] @lockable class Gas(_ResourceObjectBase): """Gas gap material defined by a mixture of gases. Args: pure_gases: A list of PureGas objects describing the types of gas in the gap. gas_fractions: A list of fractional numbers describing the volumetric fractions of gas types in the mixture. This list must align with the pure_gases input list and must sum to 1. identifier: Text string for a unique object ID. Must be a UUID in the format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. If None, a UUID will automatically be generated. (Default: None). Properties: * identifier * display_name * pure_gases * gas_fractions * gas_count * protected * user_data """ __slots__ = ('_gas_count', '_pure_gases', '_gas_fractions') def __init__(self, pure_gases, gas_fractions, identifier=None): """Initialize gas mixture material.""" _ResourceObjectBase.__init__(self, identifier) try: # check the number of gases self._gas_count = len(pure_gases) except (TypeError, ValueError): raise TypeError( 'Expected list for pure_gases. Got {}.'.format(type(pure_gases))) assert 1 <= self._gas_count, 'Number of gases in gas mixture must be ' \ 'greater than 1. Got {}.'.format(self._gas_count) self.pure_gases = pure_gases self.gas_fractions = gas_fractions @property def pure_gases(self): """Get or set a tuple of text describing the gases in the gas gap layer.""" return self._pure_gases @pure_gases.setter def pure_gases(self, value): try: if not isinstance(value, tuple): value = tuple(value) except TypeError: raise TypeError('Expected list or tuple for pure_gases. ' 'Got {}'.format(type(value))) for g in value: assert isinstance(g, PureGas), 'Expected PureGas' \ ' material for Gas. Got {}.'.format(type(g)) assert len(value) > 0, 'Gas must possess at least one pure gas.' self._pure_gases = value @property def gas_fractions(self): """Get or set a tuple of numbers the fractions of gases in the gas gap layer.""" return self._gas_fractions @gas_fractions.setter def gas_fractions(self, g_fracs): self._gas_fractions = tuple_with_length( g_fracs, self._gas_count, float, 'gas mixture gas_fractions') assert sum(self._gas_fractions) == 1, 'Gas fractions must sum to 1. ' \ 'Got {}.'.format(sum(self._gas_fractions)) @property def molecular_weight(self): """Get the gas molecular weight.""" return sum(tuple(gas.molecular_weight * frac for gas, frac in zip(self._pure_gases, self._gas_fractions))) @property def gas_count(self): """An integer indicating the number of gases in the mixture.""" return self._gas_count @property def conductivity(self): """Conductivity of the gas in the absence of convection at 0C [W/m-K].""" return self.conductivity_at_temperature(273.15) @property def viscosity(self): """Viscosity of the gas at 0C [kg/m-s].""" return self.viscosity_at_temperature(273.15) @property def specific_heat(self): """Specific heat of the gas at 0C [J/kg-K].""" return self.specific_heat_at_temperature(273.15) @property def density(self): """Density of the gas at 0C and sea-level pressure [J/kg-K].""" return self.density_at_temperature(273.15) @property def prandtl(self): """Prandtl number of the gas at 0C.""" return self.prandtl_at_temperature(273.15)
[docs] def conductivity_at_temperature(self, t_kelvin): """Get the conductivity of the gas [W/m-K] at a given Kelvin temperature.""" return self._weighted_avg_coeff_property('conductivity_coeff', t_kelvin)
[docs] def viscosity_at_temperature(self, t_kelvin): """Get the viscosity of the gas [kg/m-s] at a given Kelvin temperature.""" return self._weighted_avg_coeff_property('viscosity_coeff', t_kelvin)
[docs] def specific_heat_at_temperature(self, t_kelvin): """Get the specific heat of the gas [J/kg-K] at a given Kelvin temperature.""" return self._weighted_avg_coeff_property('specific_heat_coeff', t_kelvin)
[docs] def density_at_temperature(self, t_kelvin, pressure=101325): """Get the density of the gas [kg/m3] at a given temperature and pressure. This method uses the ideal gas law to estimate the density. Args: t_kelvin: The average temperature of the gas cavity in Kelvin. pressure: The average pressure of the gas cavity in Pa. Default is 101325 Pa for standard pressure at sea level. """ return (pressure * self.molecular_weight * 0.001) / (8.314 * t_kelvin)
[docs] def prandtl_at_temperature(self, t_kelvin): """Get the Prandtl number of the gas at a given Kelvin temperature.""" return self.viscosity_at_temperature(t_kelvin) * \ self.specific_heat_at_temperature(t_kelvin) / \ self.conductivity_at_temperature(t_kelvin)
[docs] @classmethod def from_therm_xml(cls, xml_element, pure_gases): """Create Gas from an XML element of a THERM Gas material. Args: xml_element: An XML element of a THERM Gas material. pure_gases: A dictionary with pure gas names as keys and PureGas object instances as values. These will be used to reassign the pure gases that make up this gas. """ # get the identifier, gases and initialize the object xml_uuid = xml_element.find('UUID') identifier = xml_uuid.text if len(identifier) == 31: identifier = uuid_from_therm_id(identifier) xml_comps = xml_element.find('Components') pure_gas_objs, gas_fractions = [], [] for xml_comp in xml_comps: xml_gas_name = xml_comp.find('PureGas') try: pure_gas_objs.append(pure_gases[xml_gas_name.text]) except KeyError as e: raise ValueError('Failed to find {} in pure gases.'.format(e)) xml_gas_fract = xml_comp.find('Fraction') gas_fractions.append(xml_gas_fract.text) mat = Gas(pure_gas_objs, gas_fractions, identifier=identifier) # assign the name if it is specified xml_name = xml_element.find('Name') if xml_name is not None: mat.display_name = xml_name.text xml_protect = xml_element.find('Protected') if xml_protect is not None: mat.protected = True if xml_protect.text == 'true' else False return mat
[docs] @classmethod def from_therm_xml_str(cls, xml_str, pure_gases): """Create a Gas from an XML text string of a THERM Gas. Args: xml_str: An XML text string of a THERM Gas. pure_gases: A dictionary with pure gas names as keys and PureGas object instances as values. These will be used to reassign the pure gases that make up this gas gap. """ root = ET.fromstring(xml_str) return cls.from_therm_xml(root, pure_gases)
[docs] @classmethod def from_dict(cls, data): """Create a Gas from a dictionary. Args: data: A python dictionary in the following format .. code-block:: python { 'type': 'Gas', 'identifier': 'e34f4b07-a012-4142-ae29-d7967c921c71', 'display_name': 'Argon Mixture', 'pure_gases': [{}, {}], # list of PureGas objects 'gas_fractions': [0.95, 0.05] } """ assert data['type'] == 'Gas', 'Expected Gas. Got {}.'.format(data['type']) pure_gases = [PureGas.from_dict(pg) for pg in data['pure_gases']] new_obj = cls(pure_gases, data['gas_fractions'], data['identifier']) cls._assign_optional_from_dict(new_obj, data) return new_obj
[docs] @classmethod def from_dict_abridged(cls, data, pure_gases): """Create a Gas from an abridged dictionary. Args: data: An GasAbridged dictionary. pure_gases: A dictionary with pure gas identifiers as keys and PureGas object instances as values. These will be used to reassign the pure gases that make up this gas gap. .. code-block:: python { 'type': 'GasAbridged', 'identifier': 'e34f4b07-a012-4142-ae29-d7967c921c71', 'display_name': 'Argon Mixture', 'pure_gases': [ 'ca280a4b-aba9-416f-9443-484285d52227', 'ba65b928-f766-4044-bc17-e53c42040bde' ], 'gas_fractions': [0.95, 0.05] } """ assert data['type'] == 'GasAbridged', \ 'Expected GasAbridged. Got {}.'.format(data['type']) try: pure_gas_objs = [pure_gases[mat_id] for mat_id in data['pure_gases']] except KeyError as e: raise ValueError('Failed to find {} in pure gases.'.format(e)) new_obj = cls(pure_gas_objs, data['gas_fractions'], data['identifier']) cls._assign_optional_from_dict(new_obj, data) return new_obj
@staticmethod def _assign_optional_from_dict(new_obj, data): """Assign optional attributes when serializing from dict.""" if 'display_name' in data and data['display_name'] is not None: new_obj.display_name = data['display_name'] if 'protected' in data and data['protected'] is not None: new_obj.protected = data['protected'] if 'user_data' in data and data['user_data'] is not None: new_obj.user_data = data['user_data']
[docs] def to_therm_xml(self, gases_element=None): """Get an THERM XML element of the gas. Note that this method only outputs a single element for the gas and, to write the full gas into an XML, the gas's pure gases must also be written. Args: gases_element: An optional XML Element for the Gases to which the generated objects will be added. If None, a new XML Element will be generated. .. code-block:: xml <Gas> <UUID>6c2409e9-5296-46c1-be11-9029b59a549b</UUID> <Name>Air</Name> <Protected>true</Protected> <Components> <Component> <Fraction>1</Fraction> <PureGas>Air</PureGas> </Component> </Components> </Gas> """ # create a new Materials element if one is not specified if gases_element is not None: xml_mat = ET.SubElement(gases_element, 'Gas') else: xml_mat = ET.Element('Gas') # add all of the required basic attributes xml_id = ET.SubElement(xml_mat, 'UUID') xml_id.text = self.identifier xml_name = ET.SubElement(xml_mat, 'Name') xml_name.text = self.display_name xml_protect = ET.SubElement(xml_mat, 'Protected') xml_protect.text = 'true' if self.protected else 'false' # add the gas components xml_comps = ET.SubElement(xml_mat, 'Components') for pure_gas, gas_fract in zip(self.pure_gases, self.gas_fractions): xml_comp = ET.SubElement(xml_comps, 'Component') xml_fact = ET.SubElement(xml_comp, 'Fraction') xml_fact.text = str(gas_fract) xml_gas = ET.SubElement(xml_comp, 'PureGas') xml_gas.text = pure_gas.display_name return xml_mat
[docs] def to_therm_xml_str(self): """Get an THERM XML string of the gas.""" xml_root = self.to_therm_xml() try: # try to indent the XML to make it read-able ET.indent(xml_root) return ET.tostring(xml_root, encoding='unicode') except AttributeError: # we are in Python 2 and no indent is available return ET.tostring(xml_root)
[docs] def to_dict(self, abridged=False): """Gas dictionary representation.""" base = {'type': 'Gas'} if not abridged else {'type': 'GasAbridged'} base['identifier'] = self.identifier base['pure_gases'] = \ [m.identifier for m in self.pure_gases] if abridged else \ [m.to_dict() for m in self.pure_gases] base['gas_fractions'] = self.gas_fractions if self._display_name is not None: base['display_name'] = self.display_name base['protected'] = self._protected if self._user_data is not None: base['user_data'] = self.user_data return base
[docs] def lock(self): """The lock() method will also lock the pure gases.""" self._locked = True for gas in self.pure_gases: gas.lock()
[docs] def unlock(self): """The unlock() method will also unlock the pure gases.""" self._locked = False for gas in self.pure_gases: gas.unlock()
[docs] @staticmethod def extract_all_from_xml_file(xml_file): """Extract all Gas objects from a THERM XML file. Args: xml_file: A path to an XML file containing objects Gas objects and corresponding PureGas components. Returns: A tuple with two elements - gases: A list of all Gas objects in the XML file as fairyfly_therm Gas objects. - pure_gases: A list of all PureGas objects in the XML file as fairyfly_therm PureGas objects. """ # read the file and get the root tree = ET.parse(xml_file) root = tree.getroot() # extract all of the PureGas objects pure_dict = {} for gas_obj in root: if gas_obj.tag == 'PureGas': try: xml_name = gas_obj.find('Name') pure_dict[xml_name.text] = PureGas.from_therm_xml(gas_obj) except Exception: # not a valid pure gas material pass # extract all of the gas objects gases = [] for gas_obj in root: if gas_obj.tag == 'Gas': try: gases.append(Gas.from_therm_xml(gas_obj, pure_dict)) except Exception: # not a valid gas material pass return gases, list(pure_dict.values())
def _weighted_avg_coeff_property(self, attr, t_kelvin): """Get a weighted average property given a dictionary of coefficients.""" property = [] for gas in self._pure_gases: property.append( getattr(gas, attr + '_a') + getattr(gas, attr + '_b') * t_kelvin + getattr(gas, attr + '_c') * t_kelvin ** 2) return sum(tuple(pr * frac for pr, frac in zip(property, self._gas_fractions))) def __key(self): """A tuple based on the object properties, useful for hashing.""" return (self.identifier, self.gas_fractions) + \ tuple(hash(g) for g in self.pure_gases) def __hash__(self): return hash(self.__key()) def __eq__(self, other): return isinstance(other, Gas) and self.__key() == other.__key() def __ne__(self, other): return not self.__eq__(other) def __repr__(self): return 'THERM Gas: {}'.format(self.display_name) def __copy__(self): new_obj = Gas( [g.duplicate() for g in self.pure_gases], self.gas_fractions, self.identifier) new_obj._display_name = self._display_name new_obj._protected = self._protected new_obj._user_data = None if self._user_data is None else self._user_data.copy() return new_obj