thermofluids 2.0.0__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.
@@ -0,0 +1,1191 @@
1
+ """
2
+ Written by Luke Erickson for Rebound Technology Jan 27 2015
3
+ """
4
+
5
+ from .Quantity import Quantity
6
+ from .ThermoExceptions import StatepointError
7
+ from CoolProp.CoolProp import PropsSI, HAPropsSI
8
+ import warnings
9
+ import numpy as np
10
+ from scipy.optimize import curve_fit
11
+
12
+
13
+ class Statepoint():
14
+ """
15
+ A statepoint instance represents a thermofluid statepoint. It fully characterizes a fluid at a point.
16
+ Specify and two independent intensive properties and the rest will be automatically calculated.
17
+ Some extensive properties are also logged, such as mass flow rate.
18
+ This class uses CoolProp for the backend property lookups.
19
+
20
+ Do not change the properties of a statepoint once it has been fully defined- create a new instance instead.
21
+
22
+ :param name: Name of the statepoint for your own reference. Any string
23
+ :param fluid: Any CoolProp fluid. Can be a simple option like "water" or "R404A", an incompressible fluid like "INCOMP::MMA", or a Refprop fluid, if you have access to that library, like "REFPROP-MIX:METHANOL&WATER"
24
+ :param t: Temperature in Celcius
25
+ :param p: Pressure in bar
26
+ :param x: Quality in fraction 0-1. All liquid is 0, all vapor is 1.
27
+ :param h: Enthalpy in kJ/kg. Reference points are those from CoolProp/Refprop
28
+ :param s: Entropy kJ/kg-C
29
+ :param c: Concentration (used for INCOMP and REFPROP-MIX mixtures) kg/kg
30
+ :param m: Mass flow rate kg/s
31
+ :param twb: Wet bulb temperature C
32
+ :param tdp: Dew point temperature C
33
+ :param rh: Relative humidity -
34
+ :param omega: Humidity ratio kg water/kg dry air
35
+ :param source: Another statepoint instance to import properties from
36
+ :param source_vars: A tuple of strings of the properties you'd like to import from source
37
+
38
+ All the inputs are also available as attributes. Additional dependent attributes include:
39
+ * cp: heat capacity at constant pressure kJ/kg-C
40
+ * cv: heat capacity at constant volume kJ/kg-C
41
+ * rho: density kg/m^3
42
+ * u: internal energy kJ/kg
43
+ * gamma: heat capacity ratio
44
+ * k: thermal conductivity kW/m-C
45
+ * tlsat: saturation temperature of liquid aka bubble point C (Refprop only)
46
+ * tvsat: saturation temperature of vapor aka dew point C (Refprop only)
47
+ * cvap: equilibrium concentration of vapor for a liquid kg/kg (Refprop only)
48
+ * ss: speed of sound m/s
49
+ * visc: Dynamic viscosity (mu) kg/m-s^2
50
+ * pr: Prandtl number -
51
+ * sigma: surface tension N/m
52
+ * beta: coefficient of thermal expansion C^-1
53
+ * t_freeze: freezing temperature C
54
+
55
+ Statepoints can be added or divided (see methods below).
56
+
57
+ Examples:
58
+ * ``sp = Statepoint(fluid='water', t=25., p=1.) # water at room conditions``
59
+ * ``sp = Statepoint(fluid='INCOMP::MMA', t=-20., p=20., c=0.25, m=0.2) # flow of methanol and water``
60
+ * ``sp = Statepoint(fluid='REFPROP-MIX:METHANOL&WATER', x=0.8, p=0.25, c=0.95, m=1.5)``
61
+ * ``sp = Statepoint(source=sp, source_vars=('fluid', 'm', 't'), x=0) # import everything but quality from another statepoint``
62
+
63
+ Warnings:
64
+ You can set `Statepoint.allow_warnings = False` to suppress warnings
65
+ """
66
+ out_of_bounds_flag = False
67
+ allow_warnings = True
68
+
69
+ def __init__(self, name=None, fluid=None, t=None, p=None, x=None, h=None, s=None, c=None, m=None,
70
+ twb=None, tdp=None, rh=None, omega=None, source=None, source_vars=()):
71
+ # type: (object, object) -> object
72
+ self.name = name
73
+ self.IVs = {}
74
+ self.IVs_formatted = False
75
+ self.fluid = fluid
76
+ self.coolprop_fluid = None
77
+
78
+ self.sink = []
79
+ self.source = []
80
+
81
+ self.nameisset = False
82
+
83
+ self.prints = ()
84
+
85
+ if t is not None:
86
+ self.set_t(t)
87
+ if p is not None:
88
+ self.set_p(p)
89
+ if x is not None:
90
+ self.set_x(x)
91
+ if h is not None:
92
+ self.set_h(h)
93
+ if s is not None:
94
+ self.set_s(s)
95
+ if c is not None:
96
+ if c < 0.000001:
97
+ self.set_c(0.000001)
98
+ else:
99
+ self.set_c(c)
100
+ if m is not None:
101
+ self.set_m(m)
102
+ if twb is not None:
103
+ self.set_twb(twb)
104
+ if tdp is not None:
105
+ self.set_tdp(tdp)
106
+ if rh is not None:
107
+ self.set_rh(rh)
108
+ if omega is not None:
109
+ self.set_omega(omega)
110
+ if source is not None:
111
+ self.set_source(source, source_vars)
112
+
113
+ def __add__(self, other):
114
+ """
115
+ Adding two statepoints mixes them isenthalpically, conserving mass and energy. It assumes the higher pressure fluid is expanded to the lower pressure
116
+ The function returns another statepoint instance with fluid, mass, enthalpy, and pressure (and optionally concentration) set
117
+ """
118
+ if other.__class__ is not Statepoint:
119
+ print((other.__class__))
120
+ raise TypeError('Can\'t add a Statepoint to anything other than a Statepoint')
121
+
122
+ #if other.fluid is not self.fluid: #This statement causes problems because the fluid name changes based on concentration and must be updated to remove that text before comparing
123
+ #raise StatepointError('Can\'t add statepoints of different fluids')
124
+
125
+ new = Statepoint(name=None)
126
+ new.set_fluid(self.fluid)
127
+ new.set_m(self.m + other.m)
128
+ new.set_h((self.m * self.h + other.m * other.h) / (self.m + other.m))
129
+ new.set_p(min(self.p, other.p))
130
+ if hasattr(self, 'c'):
131
+ new.set_c((self.c*self.m + other.c*other.m)/new.m)
132
+
133
+ return new
134
+
135
+ def __div__(self, other):
136
+ """
137
+ Diving a statepoint by an integer divides the mass flow rate by the integer leaving all other properties equal
138
+ :param other: an integer
139
+ :return: new statepoint
140
+ """
141
+ try:
142
+ return Statepoint(name=self.name, fluid=self.fluid, p=self.p, h=self.h, c=self.c, m=self.m/other)
143
+ except AttributeError:
144
+ return Statepoint(name=self.name, fluid=self.fluid, p=self.p, h=self.h, m=self.m/other)
145
+
146
+ def __truediv__(self, other):
147
+ """
148
+ Diving a statepoint by an integer divides the mass flow rate by the integer leaving all other properties equal
149
+ :param other: an integer
150
+ :return: new statepoint
151
+ """
152
+ try:
153
+ return Statepoint(name=self.name, fluid=self.fluid, p=self.p, h=self.h, c=self.c, m=self.m/other)
154
+ except AttributeError:
155
+ return Statepoint(name=self.name, fluid=self.fluid, p=self.p, h=self.h, m=self.m/other)
156
+
157
+ def _repr_default(self):
158
+ """Lists several useful properties of the statepoint"""
159
+ repr = ''
160
+ try:
161
+ repr += self.fluid + '\t'
162
+ except AttributeError:
163
+ pass
164
+
165
+ try:
166
+ repr += str(self.m) + '\t'
167
+ except AttributeError:
168
+ pass
169
+
170
+ try:
171
+ repr += str(self.t) + '\t'
172
+ except AttributeError:
173
+ pass
174
+
175
+ try:
176
+ repr += f'{self.c*100.: .1f}%' + '\t'
177
+ except AttributeError:
178
+ pass
179
+
180
+ try:
181
+ repr += str(self.p) + '\t'
182
+ except AttributeError:
183
+ pass
184
+
185
+ return repr
186
+
187
+ def print_options(self, tuple_of_prints=()):
188
+ self.prints = tuple_of_prints
189
+
190
+ def __repr__(self):
191
+ """Statepoint_name\tfluid_name\tmdot\ttemp\tconc\tenthalpy"""
192
+ if len(self.prints) == 0:
193
+ return self._repr_default()
194
+ else:
195
+ repr = ""
196
+ for var in self.prints:
197
+ repr = repr+'\t'+str(getattr(self, var))
198
+ return repr
199
+
200
+ def warn_out_of_bounds(self):
201
+ if Statepoint.allow_warnings:
202
+ if not Statepoint.out_of_bounds_flag:
203
+ Statepoint.out_of_bounds_flag = True
204
+ warnings.warn('Using extrapolated property data. Accuracy may be diminished.')
205
+
206
+ def set_fluid(self, fluid):
207
+ self.fluid = fluid
208
+
209
+ def set_t(self, val, unit='degC'):
210
+ if hasattr(self, '_t'):
211
+ self.update_prop(prop_val=val, prop_name='t')
212
+ else:
213
+ if val.__class__ == Quantity:
214
+ self._t = val
215
+ else:
216
+ self._t = Quantity(val, unit)
217
+ self.IVs['T'] = self._t
218
+
219
+ def set_h(self, val, unit='kJ*kg^-1'):
220
+ if hasattr(self, '_h'):
221
+ self.update_prop(prop_val=val, prop_name='h')
222
+ else:
223
+ if val.__class__ == Quantity:
224
+ self._h = val
225
+ else:
226
+ self._h = Quantity(val, unit)
227
+ self.IVs['H'] = self._h
228
+
229
+ def set_s(self, val, unit='degC^-1*kJ*kg^-1'):
230
+ if hasattr(self, '_s'):
231
+ self.update_prop(prop_val=val, prop_name='s')
232
+ else:
233
+ if val.__class__ == Quantity:
234
+ self._s = val
235
+ else:
236
+ self._s = Quantity(val, unit)
237
+ self.IVs['S'] = self._s
238
+
239
+ def set_p(self, val, unit='bar'):
240
+ if hasattr(self, '_p'):
241
+ self.update_prop(prop_val=val, prop_name='p')
242
+ else:
243
+ if val.__class__ == Quantity:
244
+ self._p = val
245
+ else:
246
+ self._p = Quantity(val, unit)
247
+ self.IVs['P'] = self._p
248
+
249
+ def set_x(self, val, unit=''):
250
+ if hasattr(self, '_x'):
251
+ self.update_prop(prop_val=val, prop_name='x')
252
+ else:
253
+ if val.__class__ == Quantity:
254
+ self._x = val
255
+ else:
256
+ self._x = Quantity(val, unit)
257
+ self.IVs['Q'] = self._x
258
+
259
+ def set_c(self, val, unit=''):
260
+ if val < 0.000001:
261
+ val = 0.000001
262
+ if val.__class__ == Quantity:
263
+ self.c = val.to('').magnitude
264
+ else:
265
+ # self.c = Quantity(val, unit)
266
+ self.c = val
267
+
268
+ def set_m(self, val, unit='kg*s^-1'):
269
+ if val.__class__ == Quantity:
270
+ self.m = val
271
+ else:
272
+ self.m = Quantity(val, unit)
273
+
274
+ def set_twb(self, val, unit='degC'):
275
+ if hasattr(self, '_twb'):
276
+ self.update_prop(prop_val=val, prop_name='twb')
277
+ else:
278
+ if val.__class__ == Quantity:
279
+ self._twb = val
280
+ else:
281
+ self._twb = Quantity(val, unit)
282
+ self.IVs['Twb'] = self._twb
283
+
284
+ def set_tdp(self, val, unit='degC'):
285
+ if hasattr(self, '_tdp'):
286
+ self.update_prop(prop_val=val, prop_name='tdp')
287
+ else:
288
+ if val.__class__ == Quantity:
289
+ self._tdp = val
290
+ else:
291
+ self._tdp = Quantity(val, unit)
292
+ self.IVs['Tdp'] = self._tdp
293
+
294
+ def set_rh(self, val, unit=''):
295
+ if hasattr(self, '_rh'):
296
+ self.update_prop(prop_val=val, prop_name='rh')
297
+ else:
298
+ if val.__class__ == Quantity:
299
+ self._rh = val
300
+ else:
301
+ self._rh = Quantity(val, unit)
302
+ self.IVs['RH'] = self._rh
303
+
304
+ def set_omega(self, val, unit=''):
305
+ if hasattr(self, '_omega'):
306
+ self.update_prop(prop_val=val, prop_name='omega')
307
+ else:
308
+ if val.__class__ == Quantity:
309
+ self._omega = val
310
+ else:
311
+ self._omega = Quantity(val, unit)
312
+ self.IVs['Omega'] = self._omega
313
+
314
+ def _formatIVs(self):
315
+ if self.fluid == None:
316
+ raise StatepointError("Fluid must be set (statepoint.set_fluid(val)) before calling properties")
317
+ self.cpargs = []
318
+ for key in self.IVs:
319
+ self.cpargs.append(key)
320
+ self.cpargs.append(self.IVs[key].to_base_units().magnitude)
321
+ if len(self.cpargs) != 4 and self.fluid != 'humid air':
322
+ raise StatepointError('%s statepoint requires 2 independent properties to be set. Only %s is set'
323
+ % (self.name, list(self.IVs.keys())))
324
+ if len(self.cpargs) != 6 and self.fluid == 'humid air':
325
+ raise StatepointError('%s statepoint requires 3 independent properties to be set. Only %s is set'
326
+ % (self.name, list(self.IVs.keys())))
327
+
328
+ # if hasattr(self, 'c') and self.fluid[-1] != ']':
329
+ if hasattr(self, 'c'):
330
+ if 'REFPROP' in self.fluid:
331
+ mw1 = PropsSI('M', 'T', 273, 'P', 101325., self.fluid[self.fluid.find('&')+1:self.fluid.__len__()])
332
+ mw2 = PropsSI('M', 'T', 273, 'P', 101325, self.fluid[self.fluid.find(':')+1:self.fluid.find('&')])
333
+ if float(self.c) == 0.0:
334
+ c_mol = 0.0
335
+ else:
336
+ c_mol = (self.c / mw2) / (self.c / mw2 + (1.-self.c) / mw1)
337
+ self.coolprop_fluid = self.fluid.replace('&', '[%s]&'%str(c_mol).strip()) + '[%s]'%str(1.0-c_mol).strip()
338
+ self.nameisset = True
339
+ else:
340
+ self.coolprop_fluid = self.fluid + '[%s]' % str(self.c).strip()
341
+ self.nameisset = True
342
+ else:
343
+ self.coolprop_fluid = self.fluid
344
+ self.nameisset = True
345
+
346
+ @property
347
+ def tmax(self): # Maximum t for dataset
348
+ if hasattr(self, '_tmax'):
349
+ return self._tmax
350
+ if not self.IVs_formatted:
351
+ self._formatIVs()
352
+ if '[' in self.fluid:
353
+ local_fluid = self.fluid.split('[')[0]
354
+ else:
355
+ local_fluid = self.fluid
356
+ if self.fluid == 'humid air':
357
+ val = 187. # not documented, just guess and tested
358
+ else:
359
+ val = PropsSI('Tmax', 'T', 298., 'P', 1.e5, local_fluid)-273.15
360
+ self._tmax = Quantity(val, 'degC')
361
+ return self._tmax
362
+
363
+ @property
364
+ def tmin(self): # Minimum t for dataset
365
+ if hasattr(self, '_tmin'):
366
+ return self._tmin
367
+ if not self.IVs_formatted:
368
+ self._formatIVs()
369
+ if '[' in self.fluid:
370
+ local_fluid = self.fluid.split('[')[0]
371
+ else:
372
+ local_fluid = self.fluid
373
+ if self.fluid == 'humid air':
374
+ val = -223. # not documented, just guess and tested
375
+ else:
376
+ val = PropsSI('Tmin', 'T', 298., 'P', 1.e5, local_fluid)-273.15
377
+ self._tmin = Quantity(val, 'degC')
378
+ return self._tmin
379
+
380
+ @property
381
+ def pmax(self): # Maximum p for dataset
382
+ if hasattr(self, '_pmax'):
383
+ return self._pmax
384
+ if not self.IVs_formatted:
385
+ self._formatIVs()
386
+ if self.fluid == 'humid air':
387
+ val = 1000. # not documented, just guess and tested
388
+ else:
389
+ val = PropsSI('Pmax', 'T', 298., 'P', 1.e5, self.coolprop_fluid) / 1.e5
390
+ self._pmax = Quantity(val, 'bar')
391
+ return self._pmax
392
+
393
+ @property
394
+ def pmin(self): # Minimum p for dataset
395
+ if hasattr(self, '_pmin'):
396
+ return self._pmin
397
+ if not self.IVs_formatted:
398
+ self._formatIVs()
399
+ if self.fluid == 'humid air':
400
+ val = 0.0001 # not documented, just guess and tested
401
+ else:
402
+ val = PropsSI('Pmin', 'T', 298., 'P', 1.e5, self.coolprop_fluid) / 1.e5
403
+ self._pmin = Quantity(val, 'bar')
404
+ return self._pmin
405
+
406
+ @property
407
+ def cmax(self): # Maximum c for dataset
408
+ if hasattr(self, '_cmax'):
409
+ return self._cmax
410
+ if not self.IVs_formatted:
411
+ self._formatIVs()
412
+ if self.fluid == 'humid air':
413
+ val = None
414
+ else:
415
+ val = PropsSI('fraction_max', 'T', 298., 'P', 1.e5, self.coolprop_fluid)
416
+ self._cmax = Quantity(val, '')
417
+ return self._cmax
418
+
419
+ @property
420
+ def cmin(self): # Minimum c for dataset
421
+ if hasattr(self, '_cmin'):
422
+ return self._cmin
423
+ if not self.IVs_formatted:
424
+ self._formatIVs()
425
+ if self.fluid == 'humid air':
426
+ val = None
427
+ else:
428
+ val = PropsSI('fraction_min', 'T', 298., 'P', 1.e5, self.coolprop_fluid)
429
+ self._cmin = Quantity(val, '')
430
+ return self._cmin
431
+
432
+ @property
433
+ def t(self): # Temperature
434
+ if hasattr(self, '_t'):
435
+ return self._t
436
+ if not self.IVs_formatted:
437
+ self._formatIVs()
438
+ try:
439
+ if self.fluid == 'humid air':
440
+ val = HAPropsSI('T', self.cpargs[0], self.cpargs[1], self.cpargs[2], self.cpargs[3], self.cpargs[4], self.cpargs[5]) - 273.15
441
+ else:
442
+ val = PropsSI('T', self.cpargs[0], self.cpargs[1], self.cpargs[2], self.cpargs[3], self.coolprop_fluid)-273.15
443
+ self._t = Quantity(val, 'degC')
444
+ return self._t
445
+ except ValueError as e:
446
+ if not hasattr(self, 'c'): # only use extrapolated data for salt brines
447
+ raise e
448
+ elif 'H' in self.cpargs and 'P' in self.cpargs:
449
+ cp_at_20C_below_max = PropsSI('C', 'T', self.tmax.to('degC').magnitude - 20. + 273.15, 'P', 1.e5, self.coolprop_fluid) / 1000.
450
+ cp_at_max = PropsSI('C', 'T', self.tmax.to('degC').magnitude + 273.15, 'P', 1.e5, self.coolprop_fluid) / 1000.
451
+ h_at_tmax = PropsSI('H', 'T', self.tmax.to('degC').magnitude + 273.15, 'P', 1.e5, self.coolprop_fluid) / 1000.
452
+
453
+ # solve the extrapolation equations for enthalpy and cp for t
454
+ val = (40.**0.5 * (10. * cp_at_max**2. + cp_at_max * (self.h.magnitude - h_at_tmax) + cp_at_20C_below_max * (h_at_tmax - self.h.magnitude))**0.5 +
455
+ cp_at_max * (self.tmax.magnitude - 20.) - cp_at_20C_below_max * self.tmax.magnitude) / (cp_at_max - cp_at_20C_below_max)
456
+ self._t = Quantity(val, 'degC')
457
+ return self._t
458
+ else:
459
+ raise ValueError(f'No temperature extrapolation data for incompressible fluids unless using P and H inputs. Got {self.cpargs} instead')
460
+
461
+ @property
462
+ def p(self): # Pressure
463
+ if hasattr(self, '_p'):
464
+ return self._p
465
+ if not self.IVs_formatted:
466
+ self._formatIVs()
467
+ if self.fluid == 'humid air':
468
+ val = HAPropsSI('P', self.cpargs[0], self.cpargs[1], self.cpargs[2], self.cpargs[3], self.cpargs[4], self.cpargs[5]) / 100000.
469
+ else:
470
+ val = PropsSI('P', self.cpargs[0], self.cpargs[1], self.cpargs[2], self.cpargs[3], self.coolprop_fluid) / 100000.
471
+ self._p = Quantity(val, 'bar')
472
+ return self._p
473
+
474
+ @property
475
+ def h(self): # Enthalpy
476
+ if hasattr(self, '_h'):
477
+ return self._h
478
+ if not self.IVs_formatted:
479
+ self._formatIVs()
480
+ if self.tmin <= self.t <= self.tmax:
481
+ if self.fluid == 'humid air':
482
+ # print(self.cpargs)
483
+ val = HAPropsSI('Hha', self.cpargs[0], self.cpargs[1], self.cpargs[2], self.cpargs[3], self.cpargs[4], self.cpargs[5]) / 1000.
484
+ else:
485
+ val = PropsSI('H', self.cpargs[0], self.cpargs[1], self.cpargs[2], self.cpargs[3], self.coolprop_fluid) / 1000.
486
+ self._h = Quantity(val, 'kJ*kg^-1')
487
+ return self._h
488
+ elif hasattr(self, 'c') and self.t > self.tmax:
489
+ """
490
+ Integrate extrapolated cp over t. See cp function for more info about extrapolation of cp.
491
+ Assumes cp extrapolation is linear.
492
+ """
493
+ self.warn_out_of_bounds()
494
+ cp_at_t = self.cp.magnitude
495
+ cp_at_tmax = PropsSI('C', 'T', self.tmax.to('degC').magnitude+273.15, 'P', 1.e5, self.coolprop_fluid) / 1000.
496
+ h_at_tmax = PropsSI('H', 'T', self.tmax.to('degC').magnitude+273.15, 'P', 1.e5, self.coolprop_fluid) / 1000.
497
+ val = h_at_tmax + (cp_at_t + cp_at_tmax) * (self.t - self.tmax).magnitude / 2.
498
+ self._h = Quantity(val, 'kJ*kg^-1')
499
+ return self._h
500
+ else:
501
+ raise ValueError(f'Temperature {self.t} is not within data range {self.tmin}-{self.tmax} and no extrapolation has been created')
502
+
503
+ @property
504
+ def s(self): # Entropy
505
+ if hasattr(self, '_s'):
506
+ return self._s
507
+ if not self.IVs_formatted:
508
+ self._formatIVs()
509
+ if self.tmin <= self.t <= self.tmax:
510
+ if self.fluid == 'humid air':
511
+ val = HAPropsSI('Sha', self.cpargs[0], self.cpargs[1], self.cpargs[2], self.cpargs[3], self.cpargs[4], self.cpargs[5]) / 1000.
512
+ else:
513
+ val = PropsSI('S', self.cpargs[0], self.cpargs[1], self.cpargs[2], self.cpargs[3], self.coolprop_fluid) / 1000.
514
+ elif hasattr(self, 'c') and self.t > self.tmax: # extrapolate for high temp salt mixes
515
+ s_at_tmax = PropsSI('S', 'T', self.tmax.to('degC').magnitude+273.15, 'P', 1.e5, self.coolprop_fluid) / 1000.
516
+ s_at_tmax_minus_10 = PropsSI('S', 'T', self.tmax.to('degC').magnitude+263.15, 'P', 1.e5, self.coolprop_fluid) / 1000.
517
+ val = s_at_tmax + (s_at_tmax - s_at_tmax_minus_10) / 10. * (self.t - self.tmax).magnitude
518
+ else:
519
+ raise ValueError('No entropy data available at these conditions, and no extrapolation has been created')
520
+ self._s = Quantity(val, 'delta_degC^-1*kJ*kg^-1')
521
+ return self._s
522
+
523
+ @property
524
+ def cp(self): # Heat capacity at const pressure
525
+ if hasattr(self, '_cp'):
526
+ return self._cp
527
+ if not self.IVs_formatted:
528
+ self._formatIVs()
529
+ if self.tmin <= self.t <= self.tmax:
530
+ if self.fluid == 'humid air':
531
+ val = HAPropsSI('Cha', self.cpargs[0], self.cpargs[1], self.cpargs[2], self.cpargs[3], self.cpargs[4], self.cpargs[5]) / 1000.
532
+ else:
533
+ val = PropsSI('C', self.cpargs[0], self.cpargs[1], self.cpargs[2], self.cpargs[3], self.coolprop_fluid) / 1000.
534
+ self._cp = Quantity(val, 'delta_degC^-1*kJ*kg^-1')
535
+ return self._cp
536
+ elif hasattr(self, 'c') and self.t > self.tmax: # extrapolate for salt mixtures above the data range
537
+ """
538
+ linearly extrapolate cp as a function of temperature, holding p and c constant
539
+ based on my (Luke's) testing, this seems to be within a few percent up to the boiling pointish
540
+ if you change this extrapolation, you also need to change the enthalpy and temperature function because they assumes linear extrapolation
541
+ """
542
+ self.warn_out_of_bounds()
543
+ cp_at_20C_below_max = PropsSI('C', 'T', self.tmax.to('degC').magnitude-20.+273.15, 'P', 1.e5, self.coolprop_fluid) / 1000.
544
+ cp_at_max = PropsSI('C', 'T', self.tmax.to('degC').magnitude+273.15, 'P', 1.e5, self.coolprop_fluid) / 1000.
545
+ val = cp_at_max + (cp_at_max - cp_at_20C_below_max) / 20. * (self.t - self.tmax).magnitude
546
+ self._cp = Quantity(val, 'delta_degC^-1*kJ*kg^-1')
547
+ return self._cp
548
+ else:
549
+ raise ValueError('Temperature %0.1f is not within data range %0.1f-%0.1f and no extrapolation has been created' % (self.t, self.tmin, self.tmax))
550
+
551
+ @property
552
+ def rho(self): # density
553
+ if hasattr(self, '_rho'):
554
+ return self._rho
555
+ if not self.IVs_formatted:
556
+ self._formatIVs()
557
+ if self.tmin <= self.t <= self.tmax:
558
+ if self.fluid == 'humid air':
559
+ val = 1. / HAPropsSI('Vha', self.cpargs[0], self.cpargs[1], self.cpargs[2], self.cpargs[3], self.cpargs[4], self.cpargs[5])
560
+ else:
561
+ val = PropsSI('D', self.cpargs[0], self.cpargs[1], self.cpargs[2], self.cpargs[3], self.coolprop_fluid)
562
+ self._rho = Quantity(val, 'kg*m^-3')
563
+ return self._rho
564
+ elif hasattr(self, 'c') and self.t > self.tmax: # extrapolate for salt mixtures above the data range
565
+ """
566
+ Assume density decreases at the same rate as pure water
567
+ """
568
+ rho_water_at_tmax = PropsSI('D', 'T', self.tmax.to('degC').magnitude+273.15, 'P', 1.e5, 'water')
569
+ if self.t < Quantity(99.6, 'degC'):
570
+ rho_water_at_t = PropsSI('D', 'T', self.t+273.15, 'P', 1.e5, 'water')
571
+ else:
572
+ rho_water_at_t = PropsSI('D', 'T', self.t+273.15, 'Q', 0., 'water')
573
+ rho_at_tmax = PropsSI('D', 'T', self.tmax.to('degC').magnitude+273.15, 'P', 1.e5, self.coolprop_fluid)
574
+ val = rho_at_tmax + rho_water_at_t - rho_water_at_tmax
575
+ self._rho = Quantity(val, 'kg*m^-3')
576
+ return self._rho
577
+ else:
578
+ raise ValueError('Temperature %0.1f is not within data range %0.1f-%0.1f and no extrapolation has been created' % (self.t, self.tmin, self.tmax))
579
+
580
+ @property
581
+ def u(self): # internal energy
582
+ if hasattr(self, '_u'):
583
+ return self._u
584
+ if not self.IVs_formatted:
585
+ self._formatIVs()
586
+ if self.fluid == 'humid air':
587
+ val = HAPropsSI('U', self.cpargs[0], self.cpargs[1], self.cpargs[2], self.cpargs[3], self.cpargs[4], self.cpargs[5]) / 1000.
588
+ else:
589
+ val = PropsSI('U', self.cpargs[0], self.cpargs[1], self.cpargs[2], self.cpargs[3], self.coolprop_fluid) / 1000.
590
+ self._u = Quantity(val, 'kJ*kg^-1')
591
+ return self._u
592
+
593
+ @property
594
+ def cv(self): # Heat capacity at const volume
595
+ if hasattr(self, '_cv'):
596
+ return self._cv
597
+ if not self.IVs_formatted:
598
+ self._formatIVs()
599
+ if self.fluid == 'humid air':
600
+ val = HAPropsSI('CVMASS', self.cpargs[0], self.cpargs[1], self.cpargs[2], self.cpargs[3], self.cpargs[4], self.cpargs[5]) / 1000.
601
+ else:
602
+ val = PropsSI('CVMASS', self.cpargs[0], self.cpargs[1], self.cpargs[2], self.cpargs[3], self.coolprop_fluid) / 1000.
603
+ self._cv = Quantity(val, 'delta_degC^-1*kJ*kg^-1')
604
+ return self._cv
605
+
606
+ @property
607
+ def gamma(self): # Heat capacity ratio
608
+ if hasattr(self, '_gamma'):
609
+ return self._gamma
610
+ self._gamma = self.cp / self.cv
611
+ return self._gamma
612
+
613
+ @property
614
+ def k(self): # Thermal conductivity
615
+ if hasattr(self, '_k'):
616
+ return self._k
617
+ if not self.IVs_formatted:
618
+ self._formatIVs()
619
+ try:
620
+ if self.fluid == 'humid air':
621
+ val = HAPropsSI('L', self.cpargs[0], self.cpargs[1], self.cpargs[2], self.cpargs[3], self.cpargs[4], self.cpargs[5]) / 1000.
622
+ else:
623
+ val = PropsSI('L', self.cpargs[0], self.cpargs[1], self.cpargs[2], self.cpargs[3], self.coolprop_fluid) / 1000.
624
+ except ValueError as e:
625
+ if 'METHANOL' in self.fluid and self.x < 0:
626
+ local_fluid = 'INCOMP::MMA[%f]' % self.c
627
+ val = PropsSI('L', self.cpargs[0], self.cpargs[1], self.cpargs[2], self.cpargs[3], local_fluid) / 1000.
628
+ elif hasattr(self, 'c') and self.fluid[:4] == 'REFP':
629
+ newFluid = self.coolprop_fluid.split(':')[1].split('&')[1].split('[')[0]
630
+ if Statepoint.allow_warnings:
631
+ warnings.warn('Using %s to get thermal conductivity because no properties available for %s' % (newFluid, self.coolprop_fluid))
632
+ if 0. <= self.x <= 1.:
633
+ # if the fluid is saturated, use the same pressure and quality with just the first fluid, not a mixture
634
+ newSP = Statepoint(fluid=newFluid, p=self.p, x=self.x)
635
+ val = newSP.k.magnitude
636
+ else:
637
+ # if the fluid is not saturated, set it to the same pressure and the same delta from the saturation temp
638
+ tSatOld = Statepoint(fluid=self.coolprop_fluid, p=self.p, x=0.).t
639
+ tSatNew = Statepoint(fluid=newFluid, p=self.p, x=0.).t
640
+ newSP = Statepoint(fluid=newFluid, p=self.p, t=tSatNew + self.t - tSatOld)
641
+ val = newSP.k.magnitude
642
+ elif hasattr(self, 'c') and self.t > self.tmax: # extrapolate for salt mixtures above the data range
643
+ """
644
+ Assume density decreases at the same rate as pure water
645
+ """
646
+ k_water_at_tmax = PropsSI('L', 'T', self.tmax.to('degC').magnitude+273.15, 'P', 1.e5, 'water') / 1000.
647
+ if self.t < 99.6:
648
+ k_water_at_t = PropsSI('L', 'T', self.t+273.15, 'P', 1.e5, 'water') / 1000.
649
+ else:
650
+ k_water_at_t = PropsSI('L', 'T', self.t+273.15, 'Q', 0., 'water') / 1000.
651
+ k_at_tmax = PropsSI('L', 'T', self.tmax.to('degC').magnitude+273.15, 'P', 1.e5, self.coolprop_fluid) / 1000.
652
+ val = k_at_tmax + k_water_at_t - k_water_at_tmax
653
+ else:
654
+ raise ValueError('Conductivity inputs are out of range and there are no extrapolations available')
655
+ self._k = Quantity(val, 'delta_degC^-1*kJ*s^-1*m^-1')
656
+ return self._k
657
+
658
+ @property
659
+ def x(self): # Quality (vapor fraction by mass)
660
+ if hasattr(self, '_x'):
661
+ return self._x
662
+ if not self.IVs_formatted:
663
+ self._formatIVs()
664
+ if self.fluid == 'humid air':
665
+ raise Exception('Quality not defined for humid air')
666
+ val = PropsSI('Q', self.cpargs[0], self.cpargs[1], self.cpargs[2], self.cpargs[3], self.coolprop_fluid)
667
+ self._x = Quantity(val, '')
668
+ return self._x
669
+
670
+ @property
671
+ def tlsat(self): #
672
+ if hasattr(self, '_tlsat'):
673
+ return self._tlsat
674
+ if not self.IVs_formatted:
675
+ self._formatIVs()
676
+ if 'REFPROP' not in self.fluid:
677
+ raise Exception('saturation properties only availible for REFPROP fluids')
678
+ val = PropsSI('T', 'Q', 0.0, 'P', self.p.to_base_units(), self.coolprop_fluid)-273.15
679
+ self._tlsat = Quantity(val, 'degC')
680
+ return self._tlsat
681
+
682
+ @property
683
+ def tvsat(self): #
684
+ if hasattr(self, '_tvsat'):
685
+ return self._tvsat
686
+ if not self.IVs_formatted:
687
+ self._formatIVs()
688
+ if 'REFPROP' not in self.fluid:
689
+ raise Exception('saturation properties only availible for REFPROP fluids')
690
+ val = PropsSI('T', 'Q', 1.0, 'P', self.p.to_base_units(), self.coolprop_fluid)-273.15
691
+ self._tvsat = Quantity(val, 'degC')
692
+ return self._tvsat
693
+
694
+ @property
695
+ def cvap(self): #
696
+ if hasattr(self, '_cvap'):
697
+ return self._cvap
698
+ if not self.IVs_formatted:
699
+ self._formatIVs()
700
+ if 'REFPROP' not in self.fluid:
701
+ raise Exception('saturation properties only availible for REFPROP fluids')
702
+
703
+ tlsat = PropsSI('T', 'Q', 0.0, 'P', self.p.to_base_units(), self.coolprop_fluid)-273.15
704
+ res = 1.
705
+ c_int = self.c
706
+ count = 0
707
+ fluid = self.coolprop_fluid
708
+ while res>0.001:
709
+ count = count + 1
710
+ fluid = fluid[0:fluid.find('[')] + fluid[fluid.find('&'):fluid.rfind('[')]
711
+
712
+ mw1 = PropsSI('M', 'T', 273, 'P', 101325., self.coolprop_fluid[self.coolprop_fluid.find('&')+1:self.coolprop_fluid.__len__()])
713
+ mw2 = PropsSI('M', 'T', 273, 'P', 101325, self.coolprop_fluid[self.coolprop_fluid.find(':')+1:self.coolprop_fluid.find('&')])
714
+ c_mol = (c_int / mw2) / (c_int / mw2 + (1.-c_int) / mw1)
715
+
716
+ fluid = fluid.replace('&', '[%s]&'%str(c_mol).strip()) + '[%s]'%str(1.-c_mol).strip()
717
+ tvsat = PropsSI('T', 'Q', 1.0, 'P', self.p.to_base_units(), fluid)-273.15
718
+ c_last = c_int
719
+ c_int = c_last+(tvsat-tlsat)/500.
720
+ res = abs((c_int-c_last)/c_int)
721
+ if count > 500:
722
+ raise Exception('vapor composition did not converge after 500 iterations')
723
+
724
+ try:
725
+ self._cvap = Quantity(c_int, '')
726
+ except:
727
+ self._cvap = c_int
728
+ return self._cvap
729
+
730
+ @property
731
+ def ss(self): # Speed of sound
732
+ if hasattr(self, '_ss'):
733
+ return self._ss
734
+ if not self.IVs_formatted:
735
+ self._formatIVs()
736
+ if self.fluid == 'humid air':
737
+ raise Exception('CoolProp doesn\'t provide data for humid air speed of sound')
738
+ val = PropsSI('speed_of_sound', self.cpargs[0], self.cpargs[1], self.cpargs[2], self.cpargs[3], self.coolprop_fluid)
739
+ self._ss = Quantity(val, 'm*s^-1')
740
+ return self._ss
741
+
742
+ @property
743
+ def visc(self): # Dyanmic viscosity
744
+ def exp_func(t, mu0, E):
745
+ return mu0 * np.exp(E / (8.314 * (t + 273.15)))
746
+
747
+ if (hasattr(self, '_visc')):
748
+ return self._visc
749
+ if not self.IVs_formatted:
750
+ self._formatIVs()
751
+ if self.tmin <= self.t <= self.tmax:
752
+ if self.fluid == 'humid air':
753
+ val = HAPropsSI('Visc', self.cpargs[0], self.cpargs[1], self.cpargs[2], self.cpargs[3], self.cpargs[4], self.cpargs[5])
754
+ else:
755
+ val = PropsSI('viscosity', self.cpargs[0], self.cpargs[1], self.cpargs[2], self.cpargs[3], self.coolprop_fluid)
756
+ self._visc = Quantity(val, 'kg*m^-1*s^-1')
757
+ return self._visc
758
+ elif hasattr(self, 'c') and self.t > self.tmax:
759
+ """Exponential curve fit to data from t=0 to tmax extrapolated out to t.
760
+ https://en.wikipedia.org/wiki/Temperature_dependence_of_liquid_viscosity#Arrhenius_model"""
761
+ ts = np.linspace(1., self.tmax.magnitude)
762
+ mu_act = np.array([Statepoint(fluid=self.fluid, t=t, p=1., c=self.c).visc.magnitude for t in ts])
763
+ m, garbage = curve_fit(exp_func, ts, mu_act)
764
+ val = exp_func(self.t.magnitude, m[0], m[1])
765
+ self._visc = Quantity(val, 'kg*m^-1*s^-1')
766
+ return self._visc
767
+ else:
768
+ raise ValueError('Temperature %0.1f is not within data range %0.1f-%0.1f and no extrapolation has been created' % (self.t, self.tmin, self.tmax))
769
+
770
+ @property
771
+ def pr(self): # Prandtl number
772
+ if (hasattr(self, '_pr')):
773
+ return self._pr
774
+ if not self.IVs_formatted:
775
+ self._formatIVs()
776
+ try: # handle viscosity being undefined for refprop mixtures with alcohols
777
+ mu = self.visc
778
+ except ValueError as e:
779
+ newFluid = self.fluid.split(':')[1].split('&')[0].split('[')[0]
780
+ if Statepoint.allow_warnings:
781
+ warnings.warn('Using %s instead because ' % newFluid + e.message)
782
+ if 0. <= self.x <= 1.:
783
+ # if the fluid is saturated, use the same pressure and quality with just the first fluid, not a mixture
784
+ newSP = Statepoint(fluid=newFluid, p=self.p, x=self.x)
785
+ mu = newSP.visc
786
+ else:
787
+ # if the fluid is not saturated, set it to the same pressure and the same delta from the saturation temp
788
+ tSatOld = Statepoint(fluid=self.fluid, p=self.p, x=0.).t
789
+ tSatNew = Statepoint(fluid=newFluid, p=self.p, x=0.).t
790
+ newSP = Statepoint(fluid=newFluid, p=self.p, t=tSatNew + self.t - tSatOld)
791
+ mu = newSP.visc
792
+ self._pr = self.cp * mu / self.k
793
+ return self._pr
794
+
795
+ @property
796
+ def sigma(self): # surface tension
797
+ if (hasattr(self, '_sigma')):
798
+ return self._sigma
799
+ if not self.IVs_formatted:
800
+ self._formatIVs()
801
+ val = PropsSI('I', self.cpargs[0], self.cpargs[1], self.cpargs[2], self.cpargs[3], self.coolprop_fluid)
802
+ self._sigma = Quantity(val, 'kg*s^-2')
803
+ return self._sigma
804
+
805
+ @property
806
+ def beta(self): # coefficient of thermal expansion
807
+ if hasattr(self, '_beta'):
808
+ return self._beta
809
+ if not self.IVs_formatted:
810
+ self._formatIVs()
811
+ try:
812
+ val = PropsSI('ISOBARIC_EXPANSION_COEFFICIENT', self.cpargs[0], self.cpargs[1], self.cpargs[2], self.cpargs[3], self.coolprop_fluid)
813
+ except ValueError:
814
+ rho1 = PropsSI('D', 'T', self.t+273.15, 'P', self.p*1.e5, self.coolprop_fluid)
815
+ rho2 = PropsSI('D', 'T', self.t+0.1+273.15, 'P', self.p*1.e5, self.coolprop_fluid)
816
+ rho3 = PropsSI('D', 'T', self.t-0.1+273.15, 'P', self.p*1.e5, self.coolprop_fluid)
817
+ val1 = (rho1 - rho2) / rho1
818
+ val2 = (rho3 - rho1) / rho1
819
+ if abs((val1 - val2) / val1) < 0.01: # check both higher and lower temps to make sure we're not at a discontinuity
820
+ val = (val1 + val2) / 2.
821
+ else:
822
+ raise ValueError('Thermal expansion coefficient calc failed- discontinuity detected. Vals: %f %f' % (val1, val2))
823
+ self._beta = Quantity(val, 'delta_degC^-1')
824
+ return self._beta
825
+
826
+ @property
827
+ def t_freeze(self): # freezing point
828
+ if hasattr(self, '_t_freeze'):
829
+ return self._t_freeze
830
+ if not self.IVs_formatted:
831
+ self._formatIVs()
832
+ val = PropsSI('T_FREEZE', self.cpargs[0], self.cpargs[1], self.cpargs[2], self.cpargs[3], self.coolprop_fluid) - 273.15
833
+ self._t_freeze = Quantity(val, 'degC')
834
+ return self._t_freeze
835
+
836
+ @property
837
+ def Z(self): # compressibility
838
+ if hasattr(self, '_Z'):
839
+ return self._Z
840
+ if not self.IVs_formatted:
841
+ self._formatIVs()
842
+ if self.fluid == 'humid air':
843
+ val = HAPropsSI('Z', self.cpargs[0], self.cpargs[1], self.cpargs[2], self.cpargs[3], self.cpargs[4], self.cpargs[5])
844
+ else:
845
+ val = PropsSI('Z', self.cpargs[0], self.cpargs[1], self.cpargs[2], self.cpargs[3], self.coolprop_fluid)
846
+ self._Z = Quantity(val, '')
847
+ return self._Z
848
+
849
+ @property
850
+ def MW(self): # molecular weight
851
+ if hasattr(self, '_MW'):
852
+ return self._MW
853
+ if not self.IVs_formatted:
854
+ self._formatIVs()
855
+ val = PropsSI('M', self.cpargs[0], self.cpargs[1], self.cpargs[2], self.cpargs[3], self.coolprop_fluid)
856
+ self._MW = Quantity(val, 'kg*mol^-1')
857
+ return self._MW
858
+
859
+ @property
860
+ def vdot(self): # volumetric flow rate
861
+ if hasattr(self, '_vdot'):
862
+ return self._vdot
863
+ if not self.IVs_formatted:
864
+ self._formatIVs()
865
+ self._vdot = self.m / self.rho
866
+ return self._vdot
867
+
868
+ @property
869
+ def twb(self):
870
+ """ Wet bulb temperature """
871
+ if self.fluid != 'humid air':
872
+ raise Exception(f'Wet bulb temperature not defined for {self.fluid}')
873
+ if hasattr(self, '_twb'):
874
+ return self._twb
875
+ if not self.IVs_formatted:
876
+ self._formatIVs()
877
+ val = HAPropsSI('Twb', self.cpargs[0], self.cpargs[1], self.cpargs[2], self.cpargs[3], self.cpargs[4], self.cpargs[5]) - 273.15
878
+ self._twb = Quantity(val, 'degC')
879
+ return self._twb
880
+
881
+ @property
882
+ def tdp(self):
883
+ """ Dew point temperature """
884
+ if self.fluid != 'humid air':
885
+ raise Exception(f'Dew point temperature not defined for {self.fluid}')
886
+ if hasattr(self, '_tdp'):
887
+ return self._tdp
888
+ if not self.IVs_formatted:
889
+ self._formatIVs()
890
+ val = HAPropsSI('Tdp', self.cpargs[0], self.cpargs[1], self.cpargs[2], self.cpargs[3], self.cpargs[4], self.cpargs[5]) - 273.15
891
+ self._tdp = Quantity(val, 'degC')
892
+ return self._tdp
893
+
894
+ @property
895
+ def rh(self):
896
+ """ Relative humidity """
897
+ if self.fluid != 'humid air':
898
+ raise Exception(f'Relative humidity not defined for {self.fluid}')
899
+ if hasattr(self, '_rh'):
900
+ return self._rh
901
+ if not self.IVs_formatted:
902
+ self._formatIVs()
903
+ val = HAPropsSI('RH', self.cpargs[0], self.cpargs[1], self.cpargs[2], self.cpargs[3], self.cpargs[4], self.cpargs[5])
904
+ self._rh = Quantity(val, '')
905
+ return self._rh
906
+
907
+ @property
908
+ def omega(self):
909
+ """ Humidity ratio, kg water / kg dry air """
910
+ if self.fluid != 'humid air':
911
+ raise Exception(f'Humidity ratio not defined for {self.fluid}')
912
+ if hasattr(self, '_omega'):
913
+ return self._omega
914
+ if not self.IVs_formatted:
915
+ self._formatIVs()
916
+ val = HAPropsSI('Omega', self.cpargs[0], self.cpargs[1], self.cpargs[2], self.cpargs[3], self.cpargs[4], self.cpargs[5])
917
+ self._omega = Quantity(val, '')
918
+ return self._omega
919
+
920
+ def set_source(self, source, setprops=()):
921
+ """
922
+ This method allows the user to create a map of statepoints and update the sink from the source properties
923
+ :param source: [Statepoint]
924
+ :param setprops: [Tuple of strings] A list of the properties that should be constant from source to sink
925
+ :return: None
926
+ """
927
+ self.source.append(source)
928
+ source.sink.append(self)
929
+ propToMethod = {'fluid': self.set_fluid, 'h': self.set_h, 't': self.set_t, 'p': self.set_p, 'x': self.set_x,
930
+ 'm': self.set_m, 'c': self.set_c, 's': self.set_s}
931
+ for prop in setprops:
932
+ if getattr(source, prop) == None:
933
+ raise StatepointError('Source does not have \'%s\' set' % prop)
934
+ else:
935
+ propToMethod[prop](getattr(source, prop)) # sets the props
936
+ return True
937
+
938
+ def set_sink(self, sink):
939
+ self.sink.append(sink)
940
+ sink.source.append(self)
941
+ return True
942
+
943
+ def update_prop(self, prop_val, prop_name):
944
+ """
945
+ This method allows the user to update a property value and have the statepoint clear its dependent variables
946
+ :param prop_val: [Float or Quantity] The updated value of the property
947
+ :param prop_name: [string] The name of the property to be updated
948
+ :return: None
949
+ """
950
+ if prop_name in ('t','T','temp','Temp','temperature','Temperature'):
951
+ if 'T' in list(self.IVs.keys()):
952
+ del self._t
953
+ if hasattr(self, '_p') and 'P' not in list(self.IVs.keys()):
954
+ del self._p
955
+ if hasattr(self, '_h') and 'H' not in list(self.IVs.keys()):
956
+ del self._h
957
+ if hasattr(self, '_k'):
958
+ del self._k
959
+ if hasattr(self, '_s') and 'S' not in list(self.IVs.keys()):
960
+ del self._s
961
+ if hasattr(self, '_x') and 'Q' not in list(self.IVs.keys()):
962
+ del self._x
963
+ if hasattr(self, '_twb') and 'Twb' not in list(self.IVs.keys()):
964
+ del self._twb
965
+ if hasattr(self, '_tdp') and 'Tdp' not in list(self.IVs.keys()):
966
+ del self._tdp
967
+ if hasattr(self, '_rh') and 'RH' not in list(self.IVs.keys()):
968
+ del self._rh
969
+ if hasattr(self, '_omega') and 'Omega' not in list(self.IVs.keys()):
970
+ del self._omega
971
+ if isinstance(prop_val, Quantity):
972
+ self._t = prop_val
973
+ else:
974
+ self._t = Quantity(prop_val, 'degC')
975
+ self.IVs['T'] = self._t
976
+ self._formatIVs()
977
+ else:
978
+ raise StatepointError('T was not an initial property input for [%s]' %self.name)
979
+
980
+ if prop_name in ('p','P','pressure','Pressure'):
981
+ if 'P' in list(self.IVs.keys()):
982
+ del self._p
983
+ if hasattr(self, '_t') and 'T' not in list(self.IVs.keys()):
984
+ del self._t
985
+ if hasattr(self, '_h') and 'H' not in list(self.IVs.keys()):
986
+ del self._h
987
+ if hasattr(self, '_k'):
988
+ del self._k
989
+ if hasattr(self, '_s') and 'S' not in list(self.IVs.keys()):
990
+ del self._s
991
+ if hasattr(self, '_x') and 'Q' not in list(self.IVs.keys()):
992
+ del self._x
993
+ if hasattr(self, '_twb') and 'Twb' not in list(self.IVs.keys()):
994
+ del self._twb
995
+ if hasattr(self, '_tdp') and 'Tdp' not in list(self.IVs.keys()):
996
+ del self._tdp
997
+ if hasattr(self, '_rh') and 'RH' not in list(self.IVs.keys()):
998
+ del self._rh
999
+ if hasattr(self, '_omega') and 'Omega' not in list(self.IVs.keys()):
1000
+ del self._omega
1001
+ if isinstance(prop_val, Quantity):
1002
+ self._p = prop_val
1003
+ else:
1004
+ self._p = Quantity(prop_val, 'bar')
1005
+ self.IVs['P'] = self._p
1006
+ self._formatIVs()
1007
+ else:
1008
+ raise StatepointError('P was not an initial property input for [%s]' %self.name)
1009
+
1010
+ if prop_name in ('h','H','enthalpy','Enthalpy'):
1011
+ if 'H' in list(self.IVs.keys()):
1012
+ del self._h
1013
+ if hasattr(self, '_t') and 'T' not in list(self.IVs.keys()):
1014
+ del self._t
1015
+ if hasattr(self, '_p') and 'P' not in list(self.IVs.keys()):
1016
+ del self._p
1017
+ if hasattr(self, '_k'):
1018
+ del self._k
1019
+ if hasattr(self, '_s') and 'S' not in list(self.IVs.keys()):
1020
+ del self._s
1021
+ if hasattr(self, '_x') and 'Q' not in list(self.IVs.keys()):
1022
+ del self._x
1023
+ if hasattr(self, '_twb') and 'Twb' not in list(self.IVs.keys()):
1024
+ del self._twb
1025
+ if hasattr(self, '_tdp') and 'Tdp' not in list(self.IVs.keys()):
1026
+ del self._tdp
1027
+ if hasattr(self, '_rh') and 'RH' not in list(self.IVs.keys()):
1028
+ del self._rh
1029
+ if hasattr(self, '_omega') and 'Omega' not in list(self.IVs.keys()):
1030
+ del self._omega
1031
+ if isinstance(prop_val, Quantity):
1032
+ self._h = prop_val
1033
+ else:
1034
+ self._h = Quantity(prop_val, 'kJ*kg^-1')
1035
+ self.IVs['H'] = self._h
1036
+ self._formatIVs()
1037
+ else:
1038
+ raise StatepointError('H was not an initial property input for [%s]' %self.name)
1039
+
1040
+ if prop_name in ('s','S','entropy','Entropy'):
1041
+ if 'S' in list(self.IVs.keys()):
1042
+ del self._s
1043
+ if hasattr(self, '_t') and 'T' not in list(self.IVs.keys()):
1044
+ del self._t
1045
+ if hasattr(self, '_p') and 'P' not in list(self.IVs.keys()):
1046
+ del self._p
1047
+ if hasattr(self, '_k'):
1048
+ del self._k
1049
+ if hasattr(self, '_h') and 'H' not in list(self.IVs.keys()):
1050
+ del self._h
1051
+ if hasattr(self, '_x') and 'Q' not in list(self.IVs.keys()):
1052
+ del self._x
1053
+ if hasattr(self, '_twb') and 'Twb' not in list(self.IVs.keys()):
1054
+ del self._twb
1055
+ if hasattr(self, '_tdp') and 'Tdp' not in list(self.IVs.keys()):
1056
+ del self._tdp
1057
+ if hasattr(self, '_rh') and 'RH' not in list(self.IVs.keys()):
1058
+ del self._rh
1059
+ if hasattr(self, '_omega') and 'Omega' not in list(self.IVs.keys()):
1060
+ del self._omega
1061
+ if isinstance(prop_val, Quantity):
1062
+ self._s = prop_val
1063
+ else:
1064
+ self._s = Quantity(prop_val, 'degC^-1*kJ*kg^-1')
1065
+ self.IVs['S'] = self._s
1066
+ self._formatIVs()
1067
+ else:
1068
+ raise StatepointError('S was not an initial property input for [%s]' %self.name)
1069
+
1070
+ if prop_name in ('X','x','quality','Quality'):
1071
+ if 'Q' in list(self.IVs.keys()):
1072
+ del self._x
1073
+ if hasattr(self, '_t') and 'T' not in list(self.IVs.keys()):
1074
+ del self._t
1075
+ if hasattr(self, '_p') and 'P' not in list(self.IVs.keys()):
1076
+ del self._p
1077
+ if hasattr(self, '_k'):
1078
+ del self._k
1079
+ if hasattr(self, '_h') and 'H' not in list(self.IVs.keys()):
1080
+ del self._h
1081
+ if hasattr(self, '_s') and 'S' not in list(self.IVs.keys()):
1082
+ del self._x
1083
+ if hasattr(self, '_twb') and 'Twb' not in list(self.IVs.keys()):
1084
+ del self._twb
1085
+ if hasattr(self, '_tdp') and 'Tdp' not in list(self.IVs.keys()):
1086
+ del self._tdp
1087
+ if hasattr(self, '_rh') and 'RH' not in list(self.IVs.keys()):
1088
+ del self._rh
1089
+ if hasattr(self, '_omega') and 'Omega' not in list(self.IVs.keys()):
1090
+ del self._omega
1091
+ if isinstance(prop_val, Quantity):
1092
+ self._x = prop_val
1093
+ else:
1094
+ self._x = Quantity(prop_val, '')
1095
+ self.IVs['Q'] = self._x
1096
+ self._formatIVs()
1097
+ else:
1098
+ raise StatepointError('X was not an initial property input for [%s]' % self.name)
1099
+
1100
+
1101
+
1102
+
1103
+
1104
+ if __name__=='__main__':
1105
+ s = Statepoint(fluid='REFPROP-MIX:METHANOL&WATER', p=1., t=10., c=0.5)
1106
+ print((s.rho))
1107
+
1108
+ s = Statepoint(fluid='REFPROP-MIX:METHANOL&WATER', p=0.11, t=10., c=0.3)
1109
+ print((s.cvap))
1110
+
1111
+
1112
+ """ Luke wrote this script to validate the salt water property extrapolations"""
1113
+ # from thermofluids import Statepoint
1114
+ # import matplotlib.pyplot as plt
1115
+ # import numpy as np
1116
+ #
1117
+ #
1118
+ # fluid = 'INCOMP::MCA'
1119
+ # t = np.arange(0., 40., step=1.)
1120
+ # c = np.arange(0., 0.3, 0.03)
1121
+ # rhos = np.zeros_like(t)
1122
+ # cps = np.zeros_like(t)
1123
+ # viscs = np.zeros_like(t)
1124
+ # hs = np.zeros_like(t)
1125
+ # ks = np.zeros_like(t)
1126
+ #
1127
+ # plt.style.use('seaborn')
1128
+ # fig, ax = plt.subplots(nrows=1, sharex=True)
1129
+ # # ax[0].set_ylabel('rho')
1130
+ # # ax[1].set_ylabel('cp')
1131
+ # # ax[2].set_ylabel('visc')
1132
+ # # ax[3].set_ylabel('h')
1133
+ # ax.set_ylabel('k')
1134
+ # ax.set_xlabel('t')
1135
+ #
1136
+ # for cx in c:
1137
+ # for i, tx in enumerate(t):
1138
+ # sp = Statepoint(fluid=fluid, t=tx, p=1., c=cx)
1139
+ # rhos[i] = sp.rho
1140
+ # cps[i] = sp.cp
1141
+ # viscs[i] = sp.visc
1142
+ # hs[i] = sp.h
1143
+ # ks[i] = sp.k
1144
+ #
1145
+ # # ax[0].plot(t, rhos, label=cx)
1146
+ # # ax[1].plot(t, cps, label=cx)
1147
+ # # ax[2].plot(t, viscs, label=cx)
1148
+ # # ax[3].plot(t, hs, label=cx)
1149
+ # ax.plot(ks, label=cx)
1150
+ #
1151
+ # t = np.arange(1., 100., 1.)
1152
+ # rhos = np.zeros_like(t)
1153
+ # cps = np.zeros_like(t)
1154
+ # viscs = np.zeros_like(t)
1155
+ # hs = np.zeros_like(t)
1156
+ # ks = np.zeros_like(t)
1157
+ # for i, tx in enumerate(t):
1158
+ # sp = Statepoint(fluid='water', t=tx, p=1.)
1159
+ # rhos[i] = sp.rho
1160
+ # cps[i] = sp.cp
1161
+ # viscs[i] = sp.visc
1162
+ # hs[i] = sp.h
1163
+ # ks[i] = sp.k
1164
+ # # ax[0].plot(t, rhos, label='Water', c='black')
1165
+ # # ax[1].plot(t, cps, label='Water', c='black')
1166
+ # # ax[2].plot(t, viscs, label='Water', c='black')
1167
+ # # ax[3].plot(t, hs, label='Water', c='black')
1168
+ # ax.plot(t, ks, label='Water', c='black')
1169
+ #
1170
+ # t = np.arange(41., 100., 1.)
1171
+ # cps = np.zeros_like(t)
1172
+ # hs = np.zeros_like(t)
1173
+ # rhos = np.zeros_like(t)
1174
+ # viscs = np.zeros_like(t)
1175
+ # ks = np.zeros_like(t)
1176
+ # for cx in c:
1177
+ # for i, tx in enumerate(t):
1178
+ # sp = Statepoint(fluid='INCOMP::MCA', t=tx, p=1., c=cx)
1179
+ # cps[i] = sp.cp
1180
+ # hs[i] = sp.h
1181
+ # rhos[i] = sp.rho
1182
+ # viscs[i] = sp.visc
1183
+ # ks[i] = sp.k
1184
+ # # ax[0].plot(t, rhos, linestyle='dashed')
1185
+ # # ax[1].plot(t, cps, linestyle='dashed')
1186
+ # # ax[2].plot(t, viscs, linestyle='dashed')
1187
+ # # ax[3].plot(t, hs, linestyle='dashed')
1188
+ # ax.plot(t, ks, linestyle='dashed')
1189
+ #
1190
+ # # plt.legend()
1191
+ # plt.show()