fairyfly-therm 0.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fairyfly_therm/__init__.py +8 -0
- fairyfly_therm/__main__.py +4 -0
- fairyfly_therm/cli/__init__.py +47 -0
- fairyfly_therm/cli/setconfig.py +75 -0
- fairyfly_therm/condition/__init__.py +1 -0
- fairyfly_therm/condition/_base.py +167 -0
- fairyfly_therm/condition/comprehensive.py +412 -0
- fairyfly_therm/config.json +6 -0
- fairyfly_therm/config.py +315 -0
- fairyfly_therm/lib/__init__.py +1 -0
- fairyfly_therm/lib/_loadconditions.py +87 -0
- fairyfly_therm/lib/_loadgases.py +98 -0
- fairyfly_therm/lib/_loadmaterials.py +98 -0
- fairyfly_therm/lib/conditions.py +24 -0
- fairyfly_therm/lib/gases.py +36 -0
- fairyfly_therm/lib/materials.py +38 -0
- fairyfly_therm/material/__init__.py +1 -0
- fairyfly_therm/material/_base.py +182 -0
- fairyfly_therm/material/cavity.py +377 -0
- fairyfly_therm/material/gas.py +925 -0
- fairyfly_therm/material/solid.py +399 -0
- fairyfly_therm/material/xmlutil.py +45 -0
- fairyfly_therm-0.3.1.dist-info/METADATA +86 -0
- fairyfly_therm-0.3.1.dist-info/RECORD +28 -0
- fairyfly_therm-0.3.1.dist-info/WHEEL +5 -0
- fairyfly_therm-0.3.1.dist-info/entry_points.txt +2 -0
- fairyfly_therm-0.3.1.dist-info/licenses/LICENSE +661 -0
- fairyfly_therm-0.3.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,925 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""Gas materials."""
|
|
3
|
+
from __future__ import division
|
|
4
|
+
import xml.etree.ElementTree as ET
|
|
5
|
+
|
|
6
|
+
from fairyfly._lockable import lockable
|
|
7
|
+
from fairyfly.typing import float_positive, float_in_range, tuple_with_length, \
|
|
8
|
+
uuid_from_therm_id
|
|
9
|
+
|
|
10
|
+
from ._base import _ResourceObjectBase
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@lockable
|
|
14
|
+
class PureGas(_ResourceObjectBase):
|
|
15
|
+
"""Custom gas gap layer.
|
|
16
|
+
|
|
17
|
+
This object allows you to specify specific values for conductivity,
|
|
18
|
+
viscosity and specific heat through the following formula:
|
|
19
|
+
|
|
20
|
+
property = A + (B * T) + (C * T ** 2)
|
|
21
|
+
|
|
22
|
+
where:
|
|
23
|
+
|
|
24
|
+
* A, B, and C = regression coefficients for the gas
|
|
25
|
+
* T = temperature [K]
|
|
26
|
+
|
|
27
|
+
Note that setting properties B and C to 0 will mean the property will be
|
|
28
|
+
equal to the A coefficient.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
conductivity_coeff_a: First conductivity coefficient.
|
|
32
|
+
Or conductivity in [W/m-K] if b and c coefficients are 0.
|
|
33
|
+
viscosity_coeff_a: First viscosity coefficient.
|
|
34
|
+
Or viscosity in [kg/m-s] if b and c coefficients are 0.
|
|
35
|
+
specific_heat_coeff_a: First specific heat coefficient.
|
|
36
|
+
Or specific heat in [J/kg-K] if b and c coefficients are 0.
|
|
37
|
+
conductivity_coeff_b: Second conductivity coefficient. Default = 0.
|
|
38
|
+
viscosity_coeff_b: Second viscosity coefficient. Default = 0.
|
|
39
|
+
specific_heat_coeff_b: Second specific heat coefficient. Default = 0.
|
|
40
|
+
conductivity_coeff_c: Third conductivity coefficient. Default = 0.
|
|
41
|
+
viscosity_coeff_c: Third viscosity coefficient. Default = 0.
|
|
42
|
+
specific_heat_coeff_c: Third specific heat coefficient. Default = 0.
|
|
43
|
+
specific_heat_ratio: A number for the the ratio of the specific heat at
|
|
44
|
+
constant pressure, to the specific heat at constant volume.
|
|
45
|
+
Default is 1.0 for Air.
|
|
46
|
+
molecular_weight: Number between 20 and 200 for the mass of 1 mol of
|
|
47
|
+
the substance in grams. Default is 20.0.
|
|
48
|
+
identifier: Text string for a unique object ID. Must be a UUID in the
|
|
49
|
+
format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. If None, a UUID will
|
|
50
|
+
automatically be generated. (Default: None).
|
|
51
|
+
|
|
52
|
+
Properties:
|
|
53
|
+
* identifier
|
|
54
|
+
* display_name
|
|
55
|
+
* conductivity_coeff_a
|
|
56
|
+
* viscosity_coeff_a
|
|
57
|
+
* specific_heat_coeff_a
|
|
58
|
+
* conductivity_coeff_b
|
|
59
|
+
* viscosity_coeff_b
|
|
60
|
+
* specific_heat_coeff_b
|
|
61
|
+
* conductivity_coeff_c
|
|
62
|
+
* viscosity_coeff_c
|
|
63
|
+
* specific_heat_coeff_c
|
|
64
|
+
* specific_heat_ratio
|
|
65
|
+
* molecular_weight
|
|
66
|
+
* protected
|
|
67
|
+
* user_data
|
|
68
|
+
|
|
69
|
+
Usage:
|
|
70
|
+
|
|
71
|
+
.. code-block:: python
|
|
72
|
+
|
|
73
|
+
co2_mat = PureGas(0.0146, 0.000014, 827.73)
|
|
74
|
+
co2_mat.display_name = 'CO2'
|
|
75
|
+
co2_gap.specific_heat_ratio = 1.4
|
|
76
|
+
co2_gap.molecular_weight = 44
|
|
77
|
+
print(co2_gap)
|
|
78
|
+
"""
|
|
79
|
+
__slots__ = ('_conductivity_coeff_a', '_viscosity_coeff_a', '_specific_heat_coeff_a',
|
|
80
|
+
'_conductivity_coeff_b', '_viscosity_coeff_b', '_specific_heat_coeff_b',
|
|
81
|
+
'_conductivity_coeff_c', '_viscosity_coeff_c', '_specific_heat_coeff_c',
|
|
82
|
+
'_specific_heat_ratio', '_molecular_weight')
|
|
83
|
+
|
|
84
|
+
def __init__(
|
|
85
|
+
self, conductivity_coeff_a, viscosity_coeff_a, specific_heat_coeff_a,
|
|
86
|
+
conductivity_coeff_b=0, viscosity_coeff_b=0, specific_heat_coeff_b=0,
|
|
87
|
+
conductivity_coeff_c=0, viscosity_coeff_c=0, specific_heat_coeff_c=0,
|
|
88
|
+
specific_heat_ratio=1.0, molecular_weight=20.0, identifier=None):
|
|
89
|
+
"""Initialize custom gas energy material."""
|
|
90
|
+
_ResourceObjectBase.__init__(self, identifier)
|
|
91
|
+
self.conductivity_coeff_a = conductivity_coeff_a
|
|
92
|
+
self.viscosity_coeff_a = viscosity_coeff_a
|
|
93
|
+
self.specific_heat_coeff_a = specific_heat_coeff_a
|
|
94
|
+
self.conductivity_coeff_b = conductivity_coeff_b
|
|
95
|
+
self.viscosity_coeff_b = viscosity_coeff_b
|
|
96
|
+
self.specific_heat_coeff_b = specific_heat_coeff_b
|
|
97
|
+
self.conductivity_coeff_c = conductivity_coeff_c
|
|
98
|
+
self.viscosity_coeff_c = viscosity_coeff_c
|
|
99
|
+
self.specific_heat_coeff_c = specific_heat_coeff_c
|
|
100
|
+
self.specific_heat_ratio = specific_heat_ratio
|
|
101
|
+
self.molecular_weight = molecular_weight
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def conductivity_coeff_a(self):
|
|
105
|
+
"""Get or set the first conductivity coefficient."""
|
|
106
|
+
return self._conductivity_coeff_a
|
|
107
|
+
|
|
108
|
+
@conductivity_coeff_a.setter
|
|
109
|
+
def conductivity_coeff_a(self, coeff):
|
|
110
|
+
self._conductivity_coeff_a = float(coeff)
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def viscosity_coeff_a(self):
|
|
114
|
+
"""Get or set the first viscosity coefficient."""
|
|
115
|
+
return self._viscosity_coeff_a
|
|
116
|
+
|
|
117
|
+
@viscosity_coeff_a.setter
|
|
118
|
+
def viscosity_coeff_a(self, coeff):
|
|
119
|
+
self._viscosity_coeff_a = float_positive(coeff)
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def specific_heat_coeff_a(self):
|
|
123
|
+
"""Get or set the first specific heat coefficient."""
|
|
124
|
+
return self._specific_heat_coeff_a
|
|
125
|
+
|
|
126
|
+
@specific_heat_coeff_a.setter
|
|
127
|
+
def specific_heat_coeff_a(self, coeff):
|
|
128
|
+
self._specific_heat_coeff_a = float_positive(coeff)
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def conductivity_coeff_b(self):
|
|
132
|
+
"""Get or set the second conductivity coefficient."""
|
|
133
|
+
return self._conductivity_coeff_b
|
|
134
|
+
|
|
135
|
+
@conductivity_coeff_b.setter
|
|
136
|
+
def conductivity_coeff_b(self, coeff):
|
|
137
|
+
self._conductivity_coeff_b = float(coeff)
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def viscosity_coeff_b(self):
|
|
141
|
+
"""Get or set the second viscosity coefficient."""
|
|
142
|
+
return self._viscosity_coeff_b
|
|
143
|
+
|
|
144
|
+
@viscosity_coeff_b.setter
|
|
145
|
+
def viscosity_coeff_b(self, coeff):
|
|
146
|
+
self._viscosity_coeff_b = float(coeff)
|
|
147
|
+
|
|
148
|
+
@property
|
|
149
|
+
def specific_heat_coeff_b(self):
|
|
150
|
+
"""Get or set the second specific heat coefficient."""
|
|
151
|
+
return self._specific_heat_coeff_b
|
|
152
|
+
|
|
153
|
+
@specific_heat_coeff_b.setter
|
|
154
|
+
def specific_heat_coeff_b(self, coeff):
|
|
155
|
+
self._specific_heat_coeff_b = float(coeff)
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def conductivity_coeff_c(self):
|
|
159
|
+
"""Get or set the third conductivity coefficient."""
|
|
160
|
+
return self._conductivity_coeff_c
|
|
161
|
+
|
|
162
|
+
@conductivity_coeff_c.setter
|
|
163
|
+
def conductivity_coeff_c(self, coeff):
|
|
164
|
+
self._conductivity_coeff_c = float(coeff)
|
|
165
|
+
|
|
166
|
+
@property
|
|
167
|
+
def viscosity_coeff_c(self):
|
|
168
|
+
"""Get or set the third viscosity coefficient."""
|
|
169
|
+
return self._viscosity_coeff_c
|
|
170
|
+
|
|
171
|
+
@viscosity_coeff_c.setter
|
|
172
|
+
def viscosity_coeff_c(self, coeff):
|
|
173
|
+
self._viscosity_coeff_c = float(coeff)
|
|
174
|
+
|
|
175
|
+
@property
|
|
176
|
+
def specific_heat_coeff_c(self):
|
|
177
|
+
"""Get or set the third specific heat coefficient."""
|
|
178
|
+
return self._specific_heat_coeff_c
|
|
179
|
+
|
|
180
|
+
@specific_heat_coeff_c.setter
|
|
181
|
+
def specific_heat_coeff_c(self, coeff):
|
|
182
|
+
self._specific_heat_coeff_c = float(coeff)
|
|
183
|
+
|
|
184
|
+
@property
|
|
185
|
+
def specific_heat_ratio(self):
|
|
186
|
+
"""Get or set the specific heat ratio."""
|
|
187
|
+
return self._specific_heat_ratio
|
|
188
|
+
|
|
189
|
+
@specific_heat_ratio.setter
|
|
190
|
+
def specific_heat_ratio(self, number):
|
|
191
|
+
number = float(number)
|
|
192
|
+
assert 1 <= number, 'Input specific_heat_ratio ({}) must be > 1.'.format(number)
|
|
193
|
+
self._specific_heat_ratio = number
|
|
194
|
+
|
|
195
|
+
@property
|
|
196
|
+
def molecular_weight(self):
|
|
197
|
+
"""Get or set the molecular weight."""
|
|
198
|
+
return self._molecular_weight
|
|
199
|
+
|
|
200
|
+
@molecular_weight.setter
|
|
201
|
+
def molecular_weight(self, number):
|
|
202
|
+
self._molecular_weight = float_in_range(
|
|
203
|
+
number, 2.0, 300.0, 'gas material molecular weight')
|
|
204
|
+
|
|
205
|
+
@property
|
|
206
|
+
def conductivity(self):
|
|
207
|
+
"""Conductivity of the gas in the absence of convection at 0C [W/m-K]."""
|
|
208
|
+
return self.conductivity_at_temperature(273.15)
|
|
209
|
+
|
|
210
|
+
@property
|
|
211
|
+
def viscosity(self):
|
|
212
|
+
"""Viscosity of the gas at 0C [kg/m-s]."""
|
|
213
|
+
return self.viscosity_at_temperature(273.15)
|
|
214
|
+
|
|
215
|
+
@property
|
|
216
|
+
def specific_heat(self):
|
|
217
|
+
"""Specific heat of the gas at 0C [J/kg-K]."""
|
|
218
|
+
return self.specific_heat_at_temperature(273.15)
|
|
219
|
+
|
|
220
|
+
@property
|
|
221
|
+
def density(self):
|
|
222
|
+
"""Density of the gas at 0C and sea-level pressure [J/kg-K]."""
|
|
223
|
+
return self.density_at_temperature(273.15)
|
|
224
|
+
|
|
225
|
+
@property
|
|
226
|
+
def prandtl(self):
|
|
227
|
+
"""Prandtl number of the gas at 0C."""
|
|
228
|
+
return self.prandtl_at_temperature(273.15)
|
|
229
|
+
|
|
230
|
+
def conductivity_at_temperature(self, t_kelvin):
|
|
231
|
+
"""Get the conductivity of the gas [W/m-K] at a given Kelvin temperature."""
|
|
232
|
+
return self.conductivity_coeff_a + self.conductivity_coeff_b * t_kelvin + \
|
|
233
|
+
self.conductivity_coeff_c * t_kelvin ** 2
|
|
234
|
+
|
|
235
|
+
def viscosity_at_temperature(self, t_kelvin):
|
|
236
|
+
"""Get the viscosity of the gas [kg/m-s] at a given Kelvin temperature."""
|
|
237
|
+
return self.viscosity_coeff_a + self.viscosity_coeff_b * t_kelvin + \
|
|
238
|
+
self.viscosity_coeff_c * t_kelvin ** 2
|
|
239
|
+
|
|
240
|
+
def specific_heat_at_temperature(self, t_kelvin):
|
|
241
|
+
"""Get the specific heat of the gas [J/kg-K] at a given Kelvin temperature."""
|
|
242
|
+
return self.specific_heat_coeff_a + self.specific_heat_coeff_b * t_kelvin + \
|
|
243
|
+
self.specific_heat_coeff_c * t_kelvin ** 2
|
|
244
|
+
|
|
245
|
+
def density_at_temperature(self, t_kelvin, pressure=101325):
|
|
246
|
+
"""Get the density of the gas [kg/m3] at a given temperature and pressure.
|
|
247
|
+
|
|
248
|
+
This method uses the ideal gas law to estimate the density.
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
t_kelvin: The average temperature of the gas cavity in Kelvin.
|
|
252
|
+
pressure: The average pressure of the gas cavity in Pa.
|
|
253
|
+
Default is 101325 Pa for standard pressure at sea level.
|
|
254
|
+
"""
|
|
255
|
+
return (pressure * self.molecular_weight * 0.001) / (8.314 * t_kelvin)
|
|
256
|
+
|
|
257
|
+
def prandtl_at_temperature(self, t_kelvin):
|
|
258
|
+
"""Get the Prandtl number of the gas at a given Kelvin temperature."""
|
|
259
|
+
return self.viscosity_at_temperature(t_kelvin) * \
|
|
260
|
+
self.specific_heat_at_temperature(t_kelvin) / \
|
|
261
|
+
self.conductivity_at_temperature(t_kelvin)
|
|
262
|
+
|
|
263
|
+
@classmethod
|
|
264
|
+
def from_therm_xml(cls, xml_element):
|
|
265
|
+
"""Create PureGas from an XML element of a THERM PureGas material.
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
xml_element: An XML element of a THERM PureGas material.
|
|
269
|
+
"""
|
|
270
|
+
# get the identifier, molecular weight and specific heat ratio
|
|
271
|
+
xml_uuid = xml_element.find('UUID')
|
|
272
|
+
identifier = xml_uuid.text
|
|
273
|
+
if len(identifier) == 31:
|
|
274
|
+
identifier = uuid_from_therm_id(identifier)
|
|
275
|
+
xml_prop = xml_element.find('Properties')
|
|
276
|
+
xml_mw = xml_prop.find('MolecularWeight')
|
|
277
|
+
molecular_weight = xml_mw.text
|
|
278
|
+
xml_shr = xml_prop.find('SpecificHeatRatio')
|
|
279
|
+
specific_heat_ratio = xml_shr.text
|
|
280
|
+
# extract the conductivity curve
|
|
281
|
+
xml_cond = xml_prop.find('Conductivity')
|
|
282
|
+
xml_c_a = xml_cond.find('A')
|
|
283
|
+
conductivity_coeff_a = xml_c_a.text
|
|
284
|
+
xml_c_b = xml_cond.find('B')
|
|
285
|
+
conductivity_coeff_b = xml_c_b.text
|
|
286
|
+
xml_c_c = xml_cond.find('C')
|
|
287
|
+
conductivity_coeff_c = xml_c_c.text
|
|
288
|
+
# extract the viscosity curve
|
|
289
|
+
xml_vis = xml_prop.find('Viscosity')
|
|
290
|
+
xml_v_a = xml_vis.find('A')
|
|
291
|
+
viscosity_coeff_a = xml_v_a.text
|
|
292
|
+
xml_v_b = xml_vis.find('B')
|
|
293
|
+
viscosity_coeff_b = xml_v_b.text
|
|
294
|
+
xml_v_c = xml_vis.find('C')
|
|
295
|
+
viscosity_coeff_c = xml_v_c.text
|
|
296
|
+
# extract the specific heat curve
|
|
297
|
+
xml_sh = xml_prop.find('SpecificHeat')
|
|
298
|
+
xml_sh_a = xml_sh.find('A')
|
|
299
|
+
specific_heat_coeff_a = xml_sh_a.text
|
|
300
|
+
xml_sh_b = xml_sh.find('B')
|
|
301
|
+
specific_heat_coeff_b = xml_sh_b.text
|
|
302
|
+
xml_sh_c = xml_sh.find('C')
|
|
303
|
+
specific_heat_coeff_c = xml_sh_c.text
|
|
304
|
+
# create the PureGas material
|
|
305
|
+
mat = PureGas(
|
|
306
|
+
conductivity_coeff_a, viscosity_coeff_a, specific_heat_coeff_a,
|
|
307
|
+
conductivity_coeff_b, viscosity_coeff_b, specific_heat_coeff_b,
|
|
308
|
+
conductivity_coeff_c, viscosity_coeff_c, specific_heat_coeff_c,
|
|
309
|
+
specific_heat_ratio, molecular_weight, identifier=identifier)
|
|
310
|
+
# assign the name if it is specified
|
|
311
|
+
xml_name = xml_element.find('Name')
|
|
312
|
+
if xml_name is not None:
|
|
313
|
+
mat.display_name = xml_name.text
|
|
314
|
+
xml_protect = xml_element.find('Protected')
|
|
315
|
+
if xml_protect is not None:
|
|
316
|
+
mat.protected = True if xml_protect.text == 'true' else False
|
|
317
|
+
return mat
|
|
318
|
+
|
|
319
|
+
@classmethod
|
|
320
|
+
def from_therm_xml_str(cls, xml_str):
|
|
321
|
+
"""Create a PureGas from an XML text string of a THERM PureGas.
|
|
322
|
+
|
|
323
|
+
Args:
|
|
324
|
+
xml_str: An XML text string of a THERM PureGas.
|
|
325
|
+
"""
|
|
326
|
+
root = ET.fromstring(xml_str)
|
|
327
|
+
return cls.from_therm_xml(root)
|
|
328
|
+
|
|
329
|
+
@classmethod
|
|
330
|
+
def from_dict(cls, data):
|
|
331
|
+
"""Create a PureGas from a dictionary.
|
|
332
|
+
|
|
333
|
+
Args:
|
|
334
|
+
data: A python dictionary in the following format
|
|
335
|
+
|
|
336
|
+
.. code-block:: python
|
|
337
|
+
|
|
338
|
+
{
|
|
339
|
+
"type": 'PureGas',
|
|
340
|
+
"identifier": '7b4a5a47-ebec-4d95-b028-a78485130c34',
|
|
341
|
+
"display_name": 'CO2'
|
|
342
|
+
"conductivity_coeff_a": 0.0146,
|
|
343
|
+
"viscosity_coeff_a": 0.000014,
|
|
344
|
+
"specific_heat_coeff_a": 827.73,
|
|
345
|
+
"specific_heat_ratio": 1.4
|
|
346
|
+
"molecular_weight": 44
|
|
347
|
+
}
|
|
348
|
+
"""
|
|
349
|
+
assert data['type'] == 'PureGas', \
|
|
350
|
+
'Expected PureGas. Got {}.'.format(data['type'])
|
|
351
|
+
con_b = 0 if 'conductivity_coeff_b' not in data else data['conductivity_coeff_b']
|
|
352
|
+
vis_b = 0 if 'viscosity_coeff_b' not in data else data['viscosity_coeff_b']
|
|
353
|
+
sph_b = 0 if 'specific_heat_coeff_b' not in data \
|
|
354
|
+
else data['specific_heat_coeff_b']
|
|
355
|
+
con_c = 0 if 'conductivity_coeff_c' not in data else data['conductivity_coeff_c']
|
|
356
|
+
vis_c = 0 if 'viscosity_coeff_c' not in data else data['viscosity_coeff_c']
|
|
357
|
+
sph_c = 0 if 'specific_heat_coeff_c' not in data \
|
|
358
|
+
else data['specific_heat_coeff_c']
|
|
359
|
+
sphr = 1.0 if 'specific_heat_ratio' not in data else data['specific_heat_ratio']
|
|
360
|
+
mw = 20.0 if 'molecular_weight' not in data else data['molecular_weight']
|
|
361
|
+
new_obj = cls(
|
|
362
|
+
data['conductivity_coeff_a'], data['viscosity_coeff_a'],
|
|
363
|
+
data['specific_heat_coeff_a'],
|
|
364
|
+
con_b, vis_b, sph_b, con_c, vis_c, sph_c, sphr, mw,
|
|
365
|
+
identifier=data['identifier'])
|
|
366
|
+
if 'display_name' in data and data['display_name'] is not None:
|
|
367
|
+
new_obj.display_name = data['display_name']
|
|
368
|
+
if 'protected' in data and data['protected'] is not None:
|
|
369
|
+
new_obj.protected = data['protected']
|
|
370
|
+
if 'user_data' in data and data['user_data'] is not None:
|
|
371
|
+
new_obj.user_data = data['user_data']
|
|
372
|
+
return new_obj
|
|
373
|
+
|
|
374
|
+
def to_therm_xml(self, gases_element=None):
|
|
375
|
+
"""Get an THERM XML element of the gas.
|
|
376
|
+
|
|
377
|
+
Args:
|
|
378
|
+
gases_element: An optional XML Element for the Gases to which the
|
|
379
|
+
generated objects will be added. If None, a new XML Element
|
|
380
|
+
will be generated.
|
|
381
|
+
|
|
382
|
+
.. code-block:: xml
|
|
383
|
+
|
|
384
|
+
<PureGas>
|
|
385
|
+
<UUID>8d33196f-f052-46e6-8353-bccb9a779f9c</UUID>
|
|
386
|
+
<Name>Air</Name>
|
|
387
|
+
<Protected>true</Protected>
|
|
388
|
+
<Properties>
|
|
389
|
+
<MolecularWeight>28.97</MolecularWeight>
|
|
390
|
+
<SpecificHeatRatio>1.4</SpecificHeatRatio>
|
|
391
|
+
<Conductivity>
|
|
392
|
+
<A>0.002873</A>
|
|
393
|
+
<B>7.76e-05</B>
|
|
394
|
+
<C>0</C>
|
|
395
|
+
</Conductivity>
|
|
396
|
+
<Viscosity>
|
|
397
|
+
<A>3.723e-06</A>
|
|
398
|
+
<B>4.94e-08</B>
|
|
399
|
+
<C>0</C>
|
|
400
|
+
</Viscosity>
|
|
401
|
+
<SpecificHeat>
|
|
402
|
+
<A>1002.737</A>
|
|
403
|
+
<B>0.012324</B>
|
|
404
|
+
<C>0</C>
|
|
405
|
+
</SpecificHeat>
|
|
406
|
+
</Properties>
|
|
407
|
+
</PureGas>
|
|
408
|
+
"""
|
|
409
|
+
# create a new Materials element if one is not specified
|
|
410
|
+
if gases_element is not None:
|
|
411
|
+
xml_mat = ET.SubElement(gases_element, 'PureGas')
|
|
412
|
+
else:
|
|
413
|
+
xml_mat = ET.Element('PureGas')
|
|
414
|
+
# add all of the required basic attributes
|
|
415
|
+
xml_id = ET.SubElement(xml_mat, 'UUID')
|
|
416
|
+
xml_id.text = self.identifier
|
|
417
|
+
xml_name = ET.SubElement(xml_mat, 'Name')
|
|
418
|
+
xml_name.text = self.display_name
|
|
419
|
+
xml_protect = ET.SubElement(xml_mat, 'Protected')
|
|
420
|
+
xml_protect.text = 'true' if self.protected else 'false'
|
|
421
|
+
xml_prop = ET.SubElement(xml_mat, 'Properties')
|
|
422
|
+
# molecular weight and specific heat ratio
|
|
423
|
+
xml_mw = ET.SubElement(xml_prop, 'MolecularWeight')
|
|
424
|
+
xml_mw.text = str(self.molecular_weight)
|
|
425
|
+
xml_shr = ET.SubElement(xml_prop, 'SpecificHeatRatio')
|
|
426
|
+
xml_shr.text = str(self.specific_heat_ratio)
|
|
427
|
+
# add the conductivity curve
|
|
428
|
+
xml_cond = ET.SubElement(xml_prop, 'Conductivity')
|
|
429
|
+
xml_cond_a = ET.SubElement(xml_cond, 'A')
|
|
430
|
+
xml_cond_a.text = str(self.conductivity_coeff_a)
|
|
431
|
+
xml_cond_b = ET.SubElement(xml_cond, 'B')
|
|
432
|
+
xml_cond_b.text = str(self.conductivity_coeff_b)
|
|
433
|
+
xml_cond_c = ET.SubElement(xml_cond, 'C')
|
|
434
|
+
xml_cond_c.text = str(self.conductivity_coeff_c)
|
|
435
|
+
# add the viscosity curve
|
|
436
|
+
xml_vis = ET.SubElement(xml_prop, 'Viscosity')
|
|
437
|
+
xml_vis_a = ET.SubElement(xml_vis, 'A')
|
|
438
|
+
xml_vis_a.text = str(self.viscosity_coeff_a)
|
|
439
|
+
xml_vis_b = ET.SubElement(xml_vis, 'B')
|
|
440
|
+
xml_vis_b.text = str(self.viscosity_coeff_b)
|
|
441
|
+
xml_vis_c = ET.SubElement(xml_vis, 'C')
|
|
442
|
+
xml_vis_c.text = str(self.viscosity_coeff_c)
|
|
443
|
+
# add the specific heat curve
|
|
444
|
+
xml_sh = ET.SubElement(xml_prop, 'SpecificHeat')
|
|
445
|
+
xml_sh_a = ET.SubElement(xml_sh, 'A')
|
|
446
|
+
xml_sh_a.text = str(self.specific_heat_coeff_a)
|
|
447
|
+
xml_sh_b = ET.SubElement(xml_sh, 'B')
|
|
448
|
+
xml_sh_b.text = str(self.specific_heat_coeff_b)
|
|
449
|
+
xml_sh_c = ET.SubElement(xml_sh, 'C')
|
|
450
|
+
xml_sh_c.text = str(self.specific_heat_coeff_c)
|
|
451
|
+
return xml_mat
|
|
452
|
+
|
|
453
|
+
def to_therm_xml_str(self):
|
|
454
|
+
"""Get an THERM XML string of the gas."""
|
|
455
|
+
xml_root = self.to_therm_xml()
|
|
456
|
+
try: # try to indent the XML to make it read-able
|
|
457
|
+
ET.indent(xml_root)
|
|
458
|
+
return ET.tostring(xml_root, encoding='unicode')
|
|
459
|
+
except AttributeError: # we are in Python 2 and no indent is available
|
|
460
|
+
return ET.tostring(xml_root)
|
|
461
|
+
|
|
462
|
+
def to_dict(self):
|
|
463
|
+
"""PureGas dictionary representation."""
|
|
464
|
+
base = {
|
|
465
|
+
'type': 'PureGas',
|
|
466
|
+
'identifier': self.identifier,
|
|
467
|
+
'conductivity_coeff_a': self.conductivity_coeff_a,
|
|
468
|
+
'viscosity_coeff_a': self.viscosity_coeff_a,
|
|
469
|
+
'specific_heat_coeff_a': self.specific_heat_coeff_a,
|
|
470
|
+
'conductivity_coeff_b': self.conductivity_coeff_b,
|
|
471
|
+
'viscosity_coeff_b': self.viscosity_coeff_b,
|
|
472
|
+
'specific_heat_coeff_b': self.specific_heat_coeff_b,
|
|
473
|
+
'conductivity_coeff_c': self.conductivity_coeff_c,
|
|
474
|
+
'viscosity_coeff_c': self.viscosity_coeff_c,
|
|
475
|
+
'specific_heat_coeff_c': self.specific_heat_coeff_c,
|
|
476
|
+
'specific_heat_ratio': self.specific_heat_ratio,
|
|
477
|
+
'molecular_weight': self.molecular_weight
|
|
478
|
+
}
|
|
479
|
+
if self._display_name is not None:
|
|
480
|
+
base['display_name'] = self.display_name
|
|
481
|
+
base['protected'] = self._protected
|
|
482
|
+
if self._user_data is not None:
|
|
483
|
+
base['user_data'] = self.user_data
|
|
484
|
+
return base
|
|
485
|
+
|
|
486
|
+
def __key(self):
|
|
487
|
+
"""A tuple based on the object properties, useful for hashing."""
|
|
488
|
+
return (self.identifier, self.conductivity_coeff_a,
|
|
489
|
+
self.viscosity_coeff_a, self.specific_heat_coeff_a,
|
|
490
|
+
self.conductivity_coeff_b, self.viscosity_coeff_b,
|
|
491
|
+
self.specific_heat_coeff_b, self.conductivity_coeff_c,
|
|
492
|
+
self.viscosity_coeff_c, self.specific_heat_coeff_c,
|
|
493
|
+
self.specific_heat_ratio, self.molecular_weight)
|
|
494
|
+
|
|
495
|
+
def __hash__(self):
|
|
496
|
+
return hash(self.__key())
|
|
497
|
+
|
|
498
|
+
def __eq__(self, other):
|
|
499
|
+
return isinstance(other, PureGas) and \
|
|
500
|
+
self.__key() == other.__key()
|
|
501
|
+
|
|
502
|
+
def __ne__(self, other):
|
|
503
|
+
return not self.__eq__(other)
|
|
504
|
+
|
|
505
|
+
def __repr__(self):
|
|
506
|
+
return 'THERM Pure Gas: {}'.format(self.display_name)
|
|
507
|
+
|
|
508
|
+
def __copy__(self):
|
|
509
|
+
new_obj = PureGas(
|
|
510
|
+
self.conductivity_coeff_a, self.viscosity_coeff_a, self.specific_heat_coeff_a,
|
|
511
|
+
self.conductivity_coeff_b, self.viscosity_coeff_b, self.specific_heat_coeff_b,
|
|
512
|
+
self.conductivity_coeff_c, self.viscosity_coeff_c, self.specific_heat_coeff_c,
|
|
513
|
+
self.specific_heat_ratio, self.molecular_weight, identifier=self.identifier)
|
|
514
|
+
new_obj._display_name = self._display_name
|
|
515
|
+
new_obj._protected = self._protected
|
|
516
|
+
new_obj._user_data = None if self._user_data is None else self._user_data.copy()
|
|
517
|
+
return new_obj
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
@lockable
|
|
521
|
+
class Gas(_ResourceObjectBase):
|
|
522
|
+
"""Gas gap material defined by a mixture of gases.
|
|
523
|
+
|
|
524
|
+
Args:
|
|
525
|
+
pure_gases: A list of PureGas objects describing the types of gas in the gap.
|
|
526
|
+
gas_fractions: A list of fractional numbers describing the volumetric
|
|
527
|
+
fractions of gas types in the mixture. This list must align with
|
|
528
|
+
the pure_gases input list and must sum to 1.
|
|
529
|
+
identifier: Text string for a unique object ID. Must be a UUID in the
|
|
530
|
+
format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. If None, a UUID will
|
|
531
|
+
automatically be generated. (Default: None).
|
|
532
|
+
|
|
533
|
+
Properties:
|
|
534
|
+
* identifier
|
|
535
|
+
* display_name
|
|
536
|
+
* pure_gases
|
|
537
|
+
* gas_fractions
|
|
538
|
+
* gas_count
|
|
539
|
+
* protected
|
|
540
|
+
* user_data
|
|
541
|
+
"""
|
|
542
|
+
__slots__ = ('_gas_count', '_pure_gases', '_gas_fractions')
|
|
543
|
+
|
|
544
|
+
def __init__(self, pure_gases, gas_fractions, identifier=None):
|
|
545
|
+
"""Initialize gas mixture material."""
|
|
546
|
+
_ResourceObjectBase.__init__(self, identifier)
|
|
547
|
+
try: # check the number of gases
|
|
548
|
+
self._gas_count = len(pure_gases)
|
|
549
|
+
except (TypeError, ValueError):
|
|
550
|
+
raise TypeError(
|
|
551
|
+
'Expected list for pure_gases. Got {}.'.format(type(pure_gases)))
|
|
552
|
+
assert 1 <= self._gas_count, 'Number of gases in gas mixture must be ' \
|
|
553
|
+
'greater than 1. Got {}.'.format(self._gas_count)
|
|
554
|
+
self.pure_gases = pure_gases
|
|
555
|
+
self.gas_fractions = gas_fractions
|
|
556
|
+
|
|
557
|
+
@property
|
|
558
|
+
def pure_gases(self):
|
|
559
|
+
"""Get or set a tuple of text describing the gases in the gas gap layer."""
|
|
560
|
+
return self._pure_gases
|
|
561
|
+
|
|
562
|
+
@pure_gases.setter
|
|
563
|
+
def pure_gases(self, value):
|
|
564
|
+
try:
|
|
565
|
+
if not isinstance(value, tuple):
|
|
566
|
+
value = tuple(value)
|
|
567
|
+
except TypeError:
|
|
568
|
+
raise TypeError('Expected list or tuple for pure_gases. '
|
|
569
|
+
'Got {}'.format(type(value)))
|
|
570
|
+
for g in value:
|
|
571
|
+
assert isinstance(g, PureGas), 'Expected PureGas' \
|
|
572
|
+
' material for Gas. Got {}.'.format(type(g))
|
|
573
|
+
assert len(value) > 0, 'Gas must possess at least one pure gas.'
|
|
574
|
+
self._pure_gases = value
|
|
575
|
+
|
|
576
|
+
@property
|
|
577
|
+
def gas_fractions(self):
|
|
578
|
+
"""Get or set a tuple of numbers the fractions of gases in the gas gap layer."""
|
|
579
|
+
return self._gas_fractions
|
|
580
|
+
|
|
581
|
+
@gas_fractions.setter
|
|
582
|
+
def gas_fractions(self, g_fracs):
|
|
583
|
+
self._gas_fractions = tuple_with_length(
|
|
584
|
+
g_fracs, self._gas_count, float, 'gas mixture gas_fractions')
|
|
585
|
+
assert sum(self._gas_fractions) == 1, 'Gas fractions must sum to 1. ' \
|
|
586
|
+
'Got {}.'.format(sum(self._gas_fractions))
|
|
587
|
+
|
|
588
|
+
@property
|
|
589
|
+
def molecular_weight(self):
|
|
590
|
+
"""Get the gas molecular weight."""
|
|
591
|
+
return sum(tuple(gas.molecular_weight * frac for gas, frac
|
|
592
|
+
in zip(self._pure_gases, self._gas_fractions)))
|
|
593
|
+
|
|
594
|
+
@property
|
|
595
|
+
def gas_count(self):
|
|
596
|
+
"""An integer indicating the number of gases in the mixture."""
|
|
597
|
+
return self._gas_count
|
|
598
|
+
|
|
599
|
+
@property
|
|
600
|
+
def conductivity(self):
|
|
601
|
+
"""Conductivity of the gas in the absence of convection at 0C [W/m-K]."""
|
|
602
|
+
return self.conductivity_at_temperature(273.15)
|
|
603
|
+
|
|
604
|
+
@property
|
|
605
|
+
def viscosity(self):
|
|
606
|
+
"""Viscosity of the gas at 0C [kg/m-s]."""
|
|
607
|
+
return self.viscosity_at_temperature(273.15)
|
|
608
|
+
|
|
609
|
+
@property
|
|
610
|
+
def specific_heat(self):
|
|
611
|
+
"""Specific heat of the gas at 0C [J/kg-K]."""
|
|
612
|
+
return self.specific_heat_at_temperature(273.15)
|
|
613
|
+
|
|
614
|
+
@property
|
|
615
|
+
def density(self):
|
|
616
|
+
"""Density of the gas at 0C and sea-level pressure [J/kg-K]."""
|
|
617
|
+
return self.density_at_temperature(273.15)
|
|
618
|
+
|
|
619
|
+
@property
|
|
620
|
+
def prandtl(self):
|
|
621
|
+
"""Prandtl number of the gas at 0C."""
|
|
622
|
+
return self.prandtl_at_temperature(273.15)
|
|
623
|
+
|
|
624
|
+
def conductivity_at_temperature(self, t_kelvin):
|
|
625
|
+
"""Get the conductivity of the gas [W/m-K] at a given Kelvin temperature."""
|
|
626
|
+
return self._weighted_avg_coeff_property('conductivity_coeff', t_kelvin)
|
|
627
|
+
|
|
628
|
+
def viscosity_at_temperature(self, t_kelvin):
|
|
629
|
+
"""Get the viscosity of the gas [kg/m-s] at a given Kelvin temperature."""
|
|
630
|
+
return self._weighted_avg_coeff_property('viscosity_coeff', t_kelvin)
|
|
631
|
+
|
|
632
|
+
def specific_heat_at_temperature(self, t_kelvin):
|
|
633
|
+
"""Get the specific heat of the gas [J/kg-K] at a given Kelvin temperature."""
|
|
634
|
+
return self._weighted_avg_coeff_property('specific_heat_coeff', t_kelvin)
|
|
635
|
+
|
|
636
|
+
def density_at_temperature(self, t_kelvin, pressure=101325):
|
|
637
|
+
"""Get the density of the gas [kg/m3] at a given temperature and pressure.
|
|
638
|
+
|
|
639
|
+
This method uses the ideal gas law to estimate the density.
|
|
640
|
+
|
|
641
|
+
Args:
|
|
642
|
+
t_kelvin: The average temperature of the gas cavity in Kelvin.
|
|
643
|
+
pressure: The average pressure of the gas cavity in Pa.
|
|
644
|
+
Default is 101325 Pa for standard pressure at sea level.
|
|
645
|
+
"""
|
|
646
|
+
return (pressure * self.molecular_weight * 0.001) / (8.314 * t_kelvin)
|
|
647
|
+
|
|
648
|
+
def prandtl_at_temperature(self, t_kelvin):
|
|
649
|
+
"""Get the Prandtl number of the gas at a given Kelvin temperature."""
|
|
650
|
+
return self.viscosity_at_temperature(t_kelvin) * \
|
|
651
|
+
self.specific_heat_at_temperature(t_kelvin) / \
|
|
652
|
+
self.conductivity_at_temperature(t_kelvin)
|
|
653
|
+
|
|
654
|
+
@classmethod
|
|
655
|
+
def from_therm_xml(cls, xml_element, pure_gases):
|
|
656
|
+
"""Create Gas from an XML element of a THERM Gas material.
|
|
657
|
+
|
|
658
|
+
Args:
|
|
659
|
+
xml_element: An XML element of a THERM Gas material.
|
|
660
|
+
pure_gases: A dictionary with pure gas names as keys and PureGas
|
|
661
|
+
object instances as values. These will be used to reassign
|
|
662
|
+
the pure gases that make up this gas.
|
|
663
|
+
"""
|
|
664
|
+
# get the identifier, gases and initialize the object
|
|
665
|
+
xml_uuid = xml_element.find('UUID')
|
|
666
|
+
identifier = xml_uuid.text
|
|
667
|
+
if len(identifier) == 31:
|
|
668
|
+
identifier = uuid_from_therm_id(identifier)
|
|
669
|
+
xml_comps = xml_element.find('Components')
|
|
670
|
+
pure_gas_objs, gas_fractions = [], []
|
|
671
|
+
for xml_comp in xml_comps:
|
|
672
|
+
xml_gas_name = xml_comp.find('PureGas')
|
|
673
|
+
try:
|
|
674
|
+
pure_gas_objs.append(pure_gases[xml_gas_name.text])
|
|
675
|
+
except KeyError as e:
|
|
676
|
+
raise ValueError('Failed to find {} in pure gases.'.format(e))
|
|
677
|
+
xml_gas_fract = xml_comp.find('Fraction')
|
|
678
|
+
gas_fractions.append(xml_gas_fract.text)
|
|
679
|
+
mat = Gas(pure_gas_objs, gas_fractions, identifier=identifier)
|
|
680
|
+
# assign the name if it is specified
|
|
681
|
+
xml_name = xml_element.find('Name')
|
|
682
|
+
if xml_name is not None:
|
|
683
|
+
mat.display_name = xml_name.text
|
|
684
|
+
xml_protect = xml_element.find('Protected')
|
|
685
|
+
if xml_protect is not None:
|
|
686
|
+
mat.protected = True if xml_protect.text == 'true' else False
|
|
687
|
+
return mat
|
|
688
|
+
|
|
689
|
+
@classmethod
|
|
690
|
+
def from_therm_xml_str(cls, xml_str, pure_gases):
|
|
691
|
+
"""Create a Gas from an XML text string of a THERM Gas.
|
|
692
|
+
|
|
693
|
+
Args:
|
|
694
|
+
xml_str: An XML text string of a THERM Gas.
|
|
695
|
+
pure_gases: A dictionary with pure gas names as keys and PureGas
|
|
696
|
+
object instances as values. These will be used to reassign
|
|
697
|
+
the pure gases that make up this gas gap.
|
|
698
|
+
"""
|
|
699
|
+
root = ET.fromstring(xml_str)
|
|
700
|
+
return cls.from_therm_xml(root, pure_gases)
|
|
701
|
+
|
|
702
|
+
@classmethod
|
|
703
|
+
def from_dict(cls, data):
|
|
704
|
+
"""Create a Gas from a dictionary.
|
|
705
|
+
|
|
706
|
+
Args:
|
|
707
|
+
data: A python dictionary in the following format
|
|
708
|
+
|
|
709
|
+
.. code-block:: python
|
|
710
|
+
|
|
711
|
+
{
|
|
712
|
+
'type': 'Gas',
|
|
713
|
+
'identifier': 'e34f4b07-a012-4142-ae29-d7967c921c71',
|
|
714
|
+
'display_name': 'Argon Mixture',
|
|
715
|
+
'pure_gases': [{}, {}], # list of PureGas objects
|
|
716
|
+
'gas_fractions': [0.95, 0.05]
|
|
717
|
+
}
|
|
718
|
+
"""
|
|
719
|
+
assert data['type'] == 'Gas', 'Expected Gas. Got {}.'.format(data['type'])
|
|
720
|
+
pure_gases = [PureGas.from_dict(pg) for pg in data['pure_gases']]
|
|
721
|
+
new_obj = cls(pure_gases, data['gas_fractions'], data['identifier'])
|
|
722
|
+
cls._assign_optional_from_dict(new_obj, data)
|
|
723
|
+
return new_obj
|
|
724
|
+
|
|
725
|
+
@classmethod
|
|
726
|
+
def from_dict_abridged(cls, data, pure_gases):
|
|
727
|
+
"""Create a Gas from an abridged dictionary.
|
|
728
|
+
|
|
729
|
+
Args:
|
|
730
|
+
data: An GasAbridged dictionary.
|
|
731
|
+
pure_gases: A dictionary with pure gas identifiers as keys and PureGas
|
|
732
|
+
object instances as values. These will be used to reassign
|
|
733
|
+
the pure gases that make up this gas gap.
|
|
734
|
+
|
|
735
|
+
.. code-block:: python
|
|
736
|
+
|
|
737
|
+
{
|
|
738
|
+
'type': 'GasAbridged',
|
|
739
|
+
'identifier': 'e34f4b07-a012-4142-ae29-d7967c921c71',
|
|
740
|
+
'display_name': 'Argon Mixture',
|
|
741
|
+
'pure_gases': [
|
|
742
|
+
'ca280a4b-aba9-416f-9443-484285d52227',
|
|
743
|
+
'ba65b928-f766-4044-bc17-e53c42040bde'
|
|
744
|
+
],
|
|
745
|
+
'gas_fractions': [0.95, 0.05]
|
|
746
|
+
}
|
|
747
|
+
"""
|
|
748
|
+
assert data['type'] == 'GasAbridged', \
|
|
749
|
+
'Expected GasAbridged. Got {}.'.format(data['type'])
|
|
750
|
+
try:
|
|
751
|
+
pure_gas_objs = [pure_gases[mat_id] for mat_id in data['pure_gases']]
|
|
752
|
+
except KeyError as e:
|
|
753
|
+
raise ValueError('Failed to find {} in pure gases.'.format(e))
|
|
754
|
+
new_obj = cls(pure_gas_objs, data['gas_fractions'], data['identifier'])
|
|
755
|
+
cls._assign_optional_from_dict(new_obj, data)
|
|
756
|
+
return new_obj
|
|
757
|
+
|
|
758
|
+
@staticmethod
|
|
759
|
+
def _assign_optional_from_dict(new_obj, data):
|
|
760
|
+
"""Assign optional attributes when serializing from dict."""
|
|
761
|
+
if 'display_name' in data and data['display_name'] is not None:
|
|
762
|
+
new_obj.display_name = data['display_name']
|
|
763
|
+
if 'protected' in data and data['protected'] is not None:
|
|
764
|
+
new_obj.protected = data['protected']
|
|
765
|
+
if 'user_data' in data and data['user_data'] is not None:
|
|
766
|
+
new_obj.user_data = data['user_data']
|
|
767
|
+
|
|
768
|
+
def to_therm_xml(self, gases_element=None):
|
|
769
|
+
"""Get an THERM XML element of the gas.
|
|
770
|
+
|
|
771
|
+
Note that this method only outputs a single element for the gas and,
|
|
772
|
+
to write the full gas into an XML, the gas's pure gases
|
|
773
|
+
must also be written.
|
|
774
|
+
|
|
775
|
+
Args:
|
|
776
|
+
gases_element: An optional XML Element for the Gases to which the
|
|
777
|
+
generated objects will be added. If None, a new XML Element
|
|
778
|
+
will be generated.
|
|
779
|
+
|
|
780
|
+
.. code-block:: xml
|
|
781
|
+
|
|
782
|
+
<Gas>
|
|
783
|
+
<UUID>6c2409e9-5296-46c1-be11-9029b59a549b</UUID>
|
|
784
|
+
<Name>Air</Name>
|
|
785
|
+
<Protected>true</Protected>
|
|
786
|
+
<Components>
|
|
787
|
+
<Component>
|
|
788
|
+
<Fraction>1</Fraction>
|
|
789
|
+
<PureGas>Air</PureGas>
|
|
790
|
+
</Component>
|
|
791
|
+
</Components>
|
|
792
|
+
</Gas>
|
|
793
|
+
"""
|
|
794
|
+
# create a new Materials element if one is not specified
|
|
795
|
+
if gases_element is not None:
|
|
796
|
+
xml_mat = ET.SubElement(gases_element, 'Gas')
|
|
797
|
+
else:
|
|
798
|
+
xml_mat = ET.Element('Gas')
|
|
799
|
+
# add all of the required basic attributes
|
|
800
|
+
xml_id = ET.SubElement(xml_mat, 'UUID')
|
|
801
|
+
xml_id.text = self.identifier
|
|
802
|
+
xml_name = ET.SubElement(xml_mat, 'Name')
|
|
803
|
+
xml_name.text = self.display_name
|
|
804
|
+
xml_protect = ET.SubElement(xml_mat, 'Protected')
|
|
805
|
+
xml_protect.text = 'true' if self.protected else 'false'
|
|
806
|
+
# add the gas components
|
|
807
|
+
xml_comps = ET.SubElement(xml_mat, 'Components')
|
|
808
|
+
for pure_gas, gas_fract in zip(self.pure_gases, self.gas_fractions):
|
|
809
|
+
xml_comp = ET.SubElement(xml_comps, 'Component')
|
|
810
|
+
xml_fact = ET.SubElement(xml_comp, 'Fraction')
|
|
811
|
+
xml_fact.text = str(gas_fract)
|
|
812
|
+
xml_gas = ET.SubElement(xml_comp, 'PureGas')
|
|
813
|
+
xml_gas.text = pure_gas.display_name
|
|
814
|
+
return xml_mat
|
|
815
|
+
|
|
816
|
+
def to_therm_xml_str(self):
|
|
817
|
+
"""Get an THERM XML string of the gas."""
|
|
818
|
+
xml_root = self.to_therm_xml()
|
|
819
|
+
try: # try to indent the XML to make it read-able
|
|
820
|
+
ET.indent(xml_root)
|
|
821
|
+
return ET.tostring(xml_root, encoding='unicode')
|
|
822
|
+
except AttributeError: # we are in Python 2 and no indent is available
|
|
823
|
+
return ET.tostring(xml_root)
|
|
824
|
+
|
|
825
|
+
def to_dict(self, abridged=False):
|
|
826
|
+
"""Gas dictionary representation."""
|
|
827
|
+
base = {'type': 'Gas'} if not abridged else {'type': 'GasAbridged'}
|
|
828
|
+
base['identifier'] = self.identifier
|
|
829
|
+
base['pure_gases'] = \
|
|
830
|
+
[m.identifier for m in self.pure_gases] if abridged else \
|
|
831
|
+
[m.to_dict() for m in self.pure_gases]
|
|
832
|
+
base['gas_fractions'] = self.gas_fractions
|
|
833
|
+
if self._display_name is not None:
|
|
834
|
+
base['display_name'] = self.display_name
|
|
835
|
+
base['protected'] = self._protected
|
|
836
|
+
if self._user_data is not None:
|
|
837
|
+
base['user_data'] = self.user_data
|
|
838
|
+
return base
|
|
839
|
+
|
|
840
|
+
def lock(self):
|
|
841
|
+
"""The lock() method will also lock the pure gases."""
|
|
842
|
+
self._locked = True
|
|
843
|
+
for gas in self.pure_gases:
|
|
844
|
+
gas.lock()
|
|
845
|
+
|
|
846
|
+
def unlock(self):
|
|
847
|
+
"""The unlock() method will also unlock the pure gases."""
|
|
848
|
+
self._locked = False
|
|
849
|
+
for gas in self.pure_gases:
|
|
850
|
+
gas.unlock()
|
|
851
|
+
|
|
852
|
+
@staticmethod
|
|
853
|
+
def extract_all_from_xml_file(xml_file):
|
|
854
|
+
"""Extract all Gas objects from a THERM XML file.
|
|
855
|
+
|
|
856
|
+
Args:
|
|
857
|
+
xml_file: A path to an XML file containing objects Gas objects and
|
|
858
|
+
corresponding PureGas components.
|
|
859
|
+
|
|
860
|
+
Returns:
|
|
861
|
+
A tuple with two elements
|
|
862
|
+
|
|
863
|
+
- gases: A list of all Gas objects in the XML file as fairyfly_therm
|
|
864
|
+
Gas objects.
|
|
865
|
+
|
|
866
|
+
- pure_gases: A list of all PureGas objects in the XML file as
|
|
867
|
+
fairyfly_therm PureGas objects.
|
|
868
|
+
"""
|
|
869
|
+
# read the file and get the root
|
|
870
|
+
tree = ET.parse(xml_file)
|
|
871
|
+
root = tree.getroot()
|
|
872
|
+
# extract all of the PureGas objects
|
|
873
|
+
pure_dict = {}
|
|
874
|
+
for gas_obj in root:
|
|
875
|
+
if gas_obj.tag == 'PureGas':
|
|
876
|
+
try:
|
|
877
|
+
xml_name = gas_obj.find('Name')
|
|
878
|
+
pure_dict[xml_name.text] = PureGas.from_therm_xml(gas_obj)
|
|
879
|
+
except Exception: # not a valid pure gas material
|
|
880
|
+
pass
|
|
881
|
+
# extract all of the gas objects
|
|
882
|
+
gases = []
|
|
883
|
+
for gas_obj in root:
|
|
884
|
+
if gas_obj.tag == 'Gas':
|
|
885
|
+
try:
|
|
886
|
+
gases.append(Gas.from_therm_xml(gas_obj, pure_dict))
|
|
887
|
+
except Exception: # not a valid gas material
|
|
888
|
+
pass
|
|
889
|
+
return gases, list(pure_dict.values())
|
|
890
|
+
|
|
891
|
+
def _weighted_avg_coeff_property(self, attr, t_kelvin):
|
|
892
|
+
"""Get a weighted average property given a dictionary of coefficients."""
|
|
893
|
+
property = []
|
|
894
|
+
for gas in self._pure_gases:
|
|
895
|
+
property.append(
|
|
896
|
+
getattr(gas, attr + '_a') +
|
|
897
|
+
getattr(gas, attr + '_b') * t_kelvin +
|
|
898
|
+
getattr(gas, attr + '_c') * t_kelvin ** 2)
|
|
899
|
+
return sum(tuple(pr * frac for pr, frac in zip(property, self._gas_fractions)))
|
|
900
|
+
|
|
901
|
+
def __key(self):
|
|
902
|
+
"""A tuple based on the object properties, useful for hashing."""
|
|
903
|
+
return (self.identifier, self.gas_fractions) + \
|
|
904
|
+
tuple(hash(g) for g in self.pure_gases)
|
|
905
|
+
|
|
906
|
+
def __hash__(self):
|
|
907
|
+
return hash(self.__key())
|
|
908
|
+
|
|
909
|
+
def __eq__(self, other):
|
|
910
|
+
return isinstance(other, Gas) and self.__key() == other.__key()
|
|
911
|
+
|
|
912
|
+
def __ne__(self, other):
|
|
913
|
+
return not self.__eq__(other)
|
|
914
|
+
|
|
915
|
+
def __repr__(self):
|
|
916
|
+
return 'THERM Gas: {}'.format(self.display_name)
|
|
917
|
+
|
|
918
|
+
def __copy__(self):
|
|
919
|
+
new_obj = Gas(
|
|
920
|
+
[g.duplicate() for g in self.pure_gases],
|
|
921
|
+
self.gas_fractions, self.identifier)
|
|
922
|
+
new_obj._display_name = self._display_name
|
|
923
|
+
new_obj._protected = self._protected
|
|
924
|
+
new_obj._user_data = None if self._user_data is None else self._user_data.copy()
|
|
925
|
+
return new_obj
|