+# -*- coding: utf-8 -*-
+"""
+Created on 21.04.2020
+
+@author: Christoph Hoeges, Fabian Wuellhorst, Jona Brach
+
+To test:
+- Transport properties are not fully tested (Status: 11.06.2020)
+- Error raising is not implemented at all refprop calls
+ - Additional change might be that not all errors and warning are excluded when 'use_error_check' is set to false but
+ only warnings or errors?
+"""
+import logging
+import os
+import warnings
+import shutil
+import atexit
+
+from ctREFPROP.ctREFPROP import REFPROPFunctionLibrary
+
+from vclibpy.media import ThermodynamicState, TransportProperties, MedProp
+
+
+logger = logging.getLogger(__name__)
+
+
+[docs]class RefProp(MedProp):
+
"""
+
Class to connect to refProp package.
+
+
Args:
+
:param string fluid_name:
+
Fluid name for RefProp use
+
:param list or None z:
+
Fluid composition. Should only be used, when a self-design mixture shall be used. Further information
+
see notes.
+
When you want to use a self-design mixture to as follows:
+
- Fluid-name needs to contain all component names within mixture: "R32.FLD|R125.FLD"
+
- z needs to be a list with molar fractions: [0.697, 0.303]
+
- Used example would be similar to R410A
+
+
:param string dll_path:
+
Specifier for the dll path used for RefProp,
+
e.g. dll_path='C:\\path_to_dll\\RefProp64.dll'.
+
If None, the `ref_prop_path` and function `get_dll_path` are
+
used to determine the dll path
+
:param boolean use_error_check:
+
Specifier whether errors and warnings shall be checked when calling RefProp or not
+
:param boolean use_warnings:
+
Specifier whether warnings shall be used
+
:param str ref_prop_path:
+
Path to RefProp package. Default is the ENV variable `RPPREFIX`.
+
:param bool copy_dll:
+
If True (not the default), a copy of the dll is created to enable simultaneous use of
+
multiple fluids in multiprocessing.
+
:param str copy_dll_directory:
+
If `copy_dll` is True, the DLL is copied to this directory.
+
If None (default), the current working directory is used.
+
+
Note:
+
- You need to install package ctREFPROP Package
+
https://github.com/usnistgov/REFPROP-wrappers/tree/master/wrappers/python
+
- In case you use a self-defined mixture: Entropy reference state might deviate from GUI!!
+
+
Functionality:
+
- It does not work to have multiple instances simultaneously. When calculating values the last fluid name
+
somehow will be used even though instance includes "new" name. Need to be fixed at some point.
+
+
+
How to use RefProp-Wrapper:
+
---------------------------
+
1.) Create RefProp instance: rp = RefProp("R32")
+
2.) In case you want to calculate fluid properties (state variables) for a specific state: Use calc_state() function
+
Multiple inputs can be given (but you need to now two in order to define the values). For further
+
information see function header.
+
3.) Further get-Functions implemented
+
- get_gwp(): Global warming potential
+
- get_odp(): Ozone depletion potential
+
- get_safety(): Safety class
+
- get_mass_fraction(): Mass fractions of pure substances in fluid
+
- get_molar_mass(): Molar mass of fluid
+
- get_mol_fraction(): Mol fractions of pure substances in fluid
+
- get_comp_names(): Pure substances names in fluid
+
- get_longname(): Long name of fluid within RefProp
+
- get_critical_point(): Crit. Temperature and Pressure
+
- get_version(): Version of wrapper and of RefProp dll
+
+
+
Version notes:
+
--------------
+
0.1.0 (21.04.2020, Christoph Hoeges):
+
First implementation
+
- Contains multiple functions to call RefProp instance
+
- Can calculate state, crit, GWP, ODP, Safety class, molar mass etc. of fluid
+
- Mixtures might work but it wasn't fully testet - R455A e.g. still deviates slightly
+
+
0.1.1 (25.04.2020, Christoph Hoeges):
+
Multiple changes, added functionality. Commands are still the same though
+
- Self designed mixtures are possible now as well (see instructions in init
+
- Added composition input to __init__ function
+
- Added modes in calc_state function and removed bug in PH calculation
+
- Additional protected functions due to different input
+
- Adjusted _call_refprop function (z is not mass frac but mol fraction)
+
- Added documentation / instruction
+
+
0.1.2 (08.05.2020, Christoph Hoeges):
+
Multiple adjustments
+
- Added function to call ABFLSHdll function in refprop (high level function)
+
- Changed function calls in calc_state function to ABFLSH and adjusted conversion
+
- Change init-function so user can choose which dll shall be used
+
- Add function to get version of this current wrapper as well as RefProp-dll version
+
+
0.1.3 (12.05.2020, Christoph Hoeges):
+
Multiple changes
+
- Added function to get all files in MIXTURE and FLUIDS directory to check available fluids
+
- Added error function in order to return errors when RefProp-Functions are called.
+
NOTE: Not all instances where refprop is called are checked for errors. Currently, it is only used in
+
init and calc_state
+
+
0.1.4 (19.05.2020, Christoph Hoeges):
+
Multiple changes:
+
- Debugged fluid properties calculation for predefined mixtures
+
- Fixed option of self-defined mixtures
+
+
0.1.5 (22.05.2020, Christoph Hoeges):
+
Include transport properties calculation into wrapper
+
+
0.1.6 (10.06.2020, Fabian Wuellhorst):
+
Add option to use a custom dll path. This necessary to use multiple instances with possible
+
different fluids at the same time.
+
"""
+
# General information
+
#
+
__version__ = "0.1.6"
+
__author__ = "Christoph Hoeges"
+
+
_fluid_mapper = {'air': 'air.ppf'}
+
+
def __init__(self,
+
fluid_name,
+
z=None,
+
dll_path: str = None,
+
use_error_check: bool = True,
+
use_warnings: bool = True,
+
ref_prop_path: str = None,
+
copy_dll: bool = True,
+
copy_dll_directory: str = None
+
):
+
if ref_prop_path is None:
+
# Get environment variable for path to dll
+
ref_prop_path = os.environ["RPPREFIX"]
+
if dll_path is None:
+
path_to_dll = self.get_dll_path(ref_prop_path)
+
else:
+
path_to_dll = dll_path
+
+
if copy_dll:
+
if copy_dll_directory is None:
+
copy_dll_directory = os.getcwd()
+
try:
+
self._delete_dll_path = os.path.join(
+
copy_dll_directory,
+
f"med_prop_{fluid_name}_REFPRP64.dll"
+
)
+
shutil.copyfile(path_to_dll, self._delete_dll_path)
+
atexit.register(self.terminate)
+
path_to_dll = self._delete_dll_path
+
except (PermissionError, FileExistsError) as err:
+
logger.error("Can't copy file to new path: %s", err)
+
else:
+
self._delete_dll_path = None
+
logger.info("Using dll: %s", path_to_dll)
+
+
super().__init__(fluid_name=fluid_name)
+
+
self._flag_check_errors = use_error_check
+
self._flag_warnings = use_warnings
+
# Set path to RefProp package
+
self._ref_prop_path = ref_prop_path
+
self.rp = REFPROPFunctionLibrary(path_to_dll)
+
self.rp.SETPATHdll(ref_prop_path)
+
self.molar_base_si = self.rp.GETENUMdll(0, "MOLAR BASE SI").iEnum
+
# Set fluid name
+
self.fluid_name = fluid_name
+
# Get mass and mol fraction and number of components
+
self._get_comp_frac(z)
+
# Get component names
+
self._comp_names = self._get_comp_names()
+
# Mixture flag
+
if self._n_comp > 1:
+
self._mix_flag = True
+
else:
+
self._mix_flag = False
+
+
# Setup
+
self._setup_rp()
+
# Calculate molar mass in kg/mol
+
# self.M = self._call_refprop_allprop("M").Output[0]
+
self.M = self._call_refprop(inp_name="", out_name="M").Output[0] # kg/mol
+
+
self._nbp = None
+
+
[docs] def terminate(self):
+
if self._delete_dll_path is not None:
+
self._delete_dll()
+
+
def _delete_dll(self):
+
try:
+
# Taken from here: https://stackoverflow.com/questions/21770419/free-the-opened-ctypes-library-in-python
+
import _ctypes
+
import sys
+
_handle = self.rp.dll._handle
+
if sys.platform.startswith('win'):
+
_ctypes.FreeLibrary(_handle)
+
else:
+
_ctypes.dlclose(_handle)
+
os.remove(self._delete_dll_path)
+
self._delete_dll_path = None
+
except (FileNotFoundError, PermissionError) as err:
+
logger.error(
+
"Could not automatically delete the copied RefProp dll at %s. "
+
"Delete it yourself! Error message: %s", self._delete_dll_path, err
+
)
+
+
def _call_refprop_abflsh(self,
+
inp_name,
+
value_a,
+
value_b,
+
i_flag=1):
+
""" Call RefProp via ABFLSHdll method
+
You can define multiple inputs but only "specific ones" where no input values are needed for
+
e.g. M, Tcrit, pcrit
+
+
Parameters:
+
-----------
+
:param string inp_name:
+
Input commands: "PQ"
+
:param float value_a:
+
Value of parameter b defined in inp_name. In case of None 0 will be used.
+
:param float value_b:
+
Value of parameter b defined in inp_name. In case of None 0 will be used.
+
:param int i_flag:
+
Flag
+
Return:
+
-------
+
:return ABFLSHdlloutput tmp:
+
Returns ABFLSH output
+
+
"""
+
# TODO
+
tmp = self.rp.ABFLSHdll(inp_name, value_a, value_b, self._mol_frac, i_flag)
+
+
return tmp
+
+
def _call_refprop_allprop(self,
+
out_name,
+
T_val=None,
+
d_val=None,
+
i_mass=0,
+
i_flag=1):
+
""" Call RefProp via ALLPROPSdll-method
+
+
Parameters:
+
-----------
+
:param string out_name:
+
Variables you want to calculate. Multiple outputs are possible:
+
- Single: "M"
+
- Multiple: "M,TC,PC"
+
:param float T_val:
+
Temperature in current state in K.
+
Note: In case you want to get general fluid parameters such as M, Tcrit, .. Stick to default value!
+
:param float d_val:
+
Density in current state (unit depending on i_mass flag - either mol/m^3 or kg/m^3)
+
Note: In case you want to get general fluid parameters such as M, Tcrit, .. Stick to default value!
+
:param int i_mass:
+
Specifies which units the inputs are given in.
+
- 0: Molar based
+
- 1: Mass based
+
Note: In current version (10.0.0.72) Ian Bell says in multiple Git-Issues that you should stick to molar
+
base!
+
:param int i_flag:
+
In current version (10.0.0.72) i_flag is used to define whether a string containing the units is written in
+
'hUnits'.
+
- 0: Deactivated (increases the calculation speed)
+
- 1: activated
+
Return:
+
-------
+
:return ALLPROPSdlloutput result:
+
List with values for parameters
+
+
"""
+
# Check values of T and d
+
if T_val is None:
+
T_val = 0
+
if d_val is None:
+
d_val = 0
+
# Define fraction used depending on i_mass flag
+
if i_mass == 0:
+
frac = self._mol_frac
+
elif i_mass == 1:
+
frac = self._mass_frac
+
else:
+
raise ValueError("Chosen i_mass flag '{}' is not possible in ALLPROPSdll function!".format(i_mass))
+
+
# Call RefProp
+
res = self.rp.ALLPROPSdll(out_name, self.molar_base_si, i_mass, i_flag, T_val, d_val, frac)
+
+
return res
+
+
def _call_refprop(self,
+
inp_name,
+
out_name,
+
value_a=None,
+
value_b=None,
+
i_mass=0,
+
i_flag=1,
+
frac=None,
+
fluid=None):
+
""" Call general refProp function and calculate values
+
+
Parameters:
+
-----------
+
:param string fluid:
+
Fluid name - in case default value None is used, stored fluid name will be used for command
+
:param string inp_name:
+
Input parameter specification
+
:param string out_name:
+
Output string name
+
:param float value_a:
+
Value of parameter b defined in inp_name. In case of None 0 will be used.
+
:param float value_b:
+
Value of parameter b defined in inp_name. In case of None 0 will be used.
+
:param integer i_flag:
+
Defines further settings (see documentation)
+
:param int i_mass:
+
Specifies which units the inputs are given in. # TODO: WRONG! iMass determines composition, iUnits determines properties (except q)
+
- 0: Molar based
+
- 1: Mass based
+
Note: In current version (10.0.0.72) Ian Bell says in multiple Git-Issues that you should stick to molar
+
base!
+
:param list frac:
+
List with either mol or mass fraction of pure substances in current fluid
+
(in case of single pure substance: [1]).
+
+
Return:
+
-------
+
:return REFPROPdlloutput output:
+
Command of refprop
+
"""
+
# Check inputs
+
if value_a is None:
+
value_a = 0
+
if value_b is None:
+
value_b = 0
+
+
if fluid is None:
+
if self._predefined:
+
fluid = self.fluid_name # TODO: in general not necessary, decreases performance
+
else:
+
fluid = ""
+
if frac is None:
+
if self._predefined:
+
frac = ""
+
else:
+
if i_mass == 0:
+
frac = self._mol_frac
+
elif i_mass == 1:
+
frac = self._mass_frac
+
else:
+
raise ValueError("Variable i_mass has invalid input '{}'".format(i_mass))
+
+
# Call refprop function
+
tmp = self.rp.REFPROPdll(fluid,
+
inp_name,
+
out_name,
+
self.molar_base_si,
+
i_mass,
+
i_flag,
+
value_a,
+
value_b,
+
frac)
+
+
return tmp
+
+
def _check_error(self,
+
err_num,
+
err_msg,
+
func_name=""):
+
""" Check error code and raise error in case it is critical
+
+
Parameters:
+
-----------
+
:param integer err_num:
+
Error return code
+
:param string err_msg:
+
Error message given in RefProp call
+
:param string func_name:
+
Name of function error needs to be checked in
+
"""
+
# All fine in case error number is 0
+
# Throw warning in case error number different than 0 and smaller than 100 is given
+
# Throw error in case error number higher than 100 is given
+
if err_num:
+
if err_num < 100:
+
if self._flag_warnings:
+
warnings.warn("[WARNING] Error number {} was given in function '{}'. No critical error but "
+
"something went wrong maybe. \n Error message is: '{}'".format(str(err_num),
+
func_name, err_msg))
+
else:
+
if self._flag_check_errors:
+
raise TypeError("[ERROR] When calling RefProp in function '{}' error number {} was "
+
"returned. \n Error message is: '{}'".format(func_name, str(err_num), err_msg))
+
+
def _get_comp_names(self):
+
""" Get component names. In case current fluid is mixture, only component names will be returned.
+
In case fluid is a pure substance, substance name is returned.
+
+
Return:
+
-------
+
:return list comp_names:
+
List with pure substances in current refrigerant
+
"""
+
comp_names = []
+
if self._predefined:
+
# Note: While trying it was possible to get fluid name as well, therefore n_comp+1 is used.
+
if self._n_comp > 1:
+
for i in range(1, self._n_comp + 3):
+
test = self.rp.NAMEdll(i)
+
tmp = test.hn80.replace(".FLD", "")
+
if not tmp == "":
+
comp_names.append(tmp)
+
else:
+
for i in range(self._n_comp + 3):
+
tmp = self.rp.NAMEdll(i).hnam
+
if not tmp == "":
+
comp_names.append(tmp)
+
+
else:
+
# Self-defined
+
tmp_str = self.fluid_name.split("|")
+
for i in tmp_str:
+
i = i.replace(".FLD", "")
+
i = i.replace(".MIX", "")
+
if len(i) < 2:
+
continue
+
else:
+
comp_names.append(i)
+
+
# Replace Carbon Dioxide for CO2
+
for i, tmp in enumerate(comp_names):
+
if "Carbon dio" in tmp:
+
comp_names[i] = "CO2"
+
+
return comp_names
+
+
def _get_comp_frac(self, z):
+
""" Get mass/mol fraction and number of components of current fluid
+
+
Parameters:
+
-----------
+
:param list z:
+
Contains predefined molar fractions in case one is given. Otherwise, z will be None
+
"""
+
# Check if predefined or not
+
# Predefined
+
if z is None:
+
self._predefined = True
+
# Pure substance
+
elif len(z) == 1:
+
self._predefined = True
+
# Self-designed mixture
+
else:
+
self._predefined = False
+
+
# In case predefined mixture or pure substance is used
+
if self._predefined:
+
# Dummy function to get values for z in order to specify number of components
+
tmp_mol = self.rp.REFPROPdll(self.fluid_name, "PQ", "H", self.molar_base_si, 0, 0, 101325, 0, [1])
+
tmp_mass = self.rp.REFPROPdll(self.fluid_name, "PQ", "H", self.molar_base_si, 1, 0, 101325, 0, [])
+
# Check for errors
+
self._check_error(tmp_mol.ierr, tmp_mol.herr, self._get_comp_frac.__name__)
+
self._check_error(tmp_mass.ierr, tmp_mass.herr, self._get_comp_frac.__name__)
+
# Mass and molar fractions of components
+
self._mol_frac = [zi for zi in tmp_mol.z if zi > 0]
+
self._mass_frac = [zi for zi in tmp_mass.z if zi > 0]
+
# Get number of components
+
self._n_comp = len(self._mol_frac)
+
# Check, whether error occurred
+
if self._n_comp < 1:
+
# It might be possible that z value bugs when calling RefProp. In case of a pure substance this does not
+
# matter so an additional filter is included
+
if len(self._mass_frac) == 1:
+
self._n_comp = 1
+
self._mol_frac = [1]
+
else:
+
raise ValueError("Number of components for current fluid '{}' is less than "
+
"one!".format(self.fluid_name))
+
+
# Get mol fraction
+
# self._mol_frac = self._transform_to_molfraction(self._mass_frac)
+
else:
+
# Mol fraction
+
self._mol_frac = z
+
self._mass_frac = []
+
# Get number of components
+
self._n_comp = len(self._mol_frac)
+
+
def _setup_rp(self):
+
""" Setup for RefProp """
+
# Errors can occur in case REFPROP is initalizated multiple time with same fluid - thus a pre setup is used here
+
self.rp.SETUPdll(1, "N2", "HMX.BNC", "DEF")
+
# In case of pure substance
+
if self._n_comp == 1:
+
self.rp.SETUPdll(self._n_comp, self.fluid_name, "HMX.BNC", "DEF")
+
# In case of mixtures
+
else:
+
# Check if mixture is predefined
+
if self._predefined:
+
# Pre defined mixture - different baseline operating point is used
+
mode = 2
+
mixture = "|".join([f+".FLD" for f in self._comp_names])
+
n_comp = self._n_comp
+
else:
+
# Self defined mixture
+
mode = 1
+
n_comp = self._n_comp
+
# Define mixtures name
+
# TODO: Ending is not necessary to create mixtures....
+
mixture = "|".join([f+".FLD" for f in self._comp_names])
+
+
# Setup for mixture
+
setup = self.rp.SETUPdll(n_comp, mixture, 'HMX.BNC', 'DEF')
+
setref = self.rp.SETREFdll("DEF", mode, self._mol_frac, 0, 0, 0, 0)
+
# z = self._mol_frac
+
# Get mass fraction
+
self._mass_frac = self._transform_to_massfraction(self._mol_frac)
+
+
# Check whether mixing rules are available
+
if setup.ierr == 117:
+
if self._flag_check_errors:
+
raise ValueError(
+
"[MIXING ERROR] Mixing rules for mixture '{}' do not exist!".format(self._comp_names))
+
else:
+
print(
+
"[MIXING ERROR] Mixing rules for mixture '{}' do not exist!".format(self._comp_names))
+
elif setup.ierr == -117:
+
if self._flag_warnings:
+
warnings.warn(
+
"[MIXING ERROR] Mixing rules for mixture '{}' are estimated!".format(self._comp_names))
+
else:
+
print(
+
"[MIXING WARNING] Mixing rules for mixture '{}' are estimated!".format(self._comp_names))
+
+
def _transform_to_massfraction(self,
+
mol_frac):
+
""" Transforms mol fraction to mass fraction
+
+
Parameters:
+
-----------
+
:param list mol_frac:
+
List containing floats for mol fraction
+
+
Return:
+
-------
+
:return list mass_frac:
+
List containing floats for mass fraction
+
"""
+
tmp = self.rp.XMASSdll(mol_frac)
+
mass_frac = [yi for yi in tmp.xkg if yi > 0]
+
return mass_frac
+
+
def _transform_to_molfraction(self,
+
mass_frac):
+
""" Transforms mass fraction to mol fraction
+
+
Parameters:
+
-----------
+
:param list mass_frac:
+
List containing floats for mass fraction
+
+
Return:
+
-------
+
:return list frac:
+
List containing floats for mol fraction
+
"""
+
tmp = self.rp.XMOLEdll(mass_frac)
+
mol_frac = [xi for xi in tmp.xmol if xi > 0]
+
return mol_frac
+
+
[docs] def calc_state(self, mode: str, var1: float, var2: float, kr=1):
+
""" Calculate state. Depending on mode, different function will be chosen. Input state variables need to be in
+
SI units!
+
+
Notes:
+
------
+
1.) PT does not work when state might be within the two-phase region!
+
2.) Only functions for density are implemented. In case you know the specific volume instead use density
+
functions with inverse value!
+
3.) Quality can have values outside of 'physical' scope:
+
q = -998: Subcooled liquid
+
q = 998: Superheated vapor
+
q = 999: Supercritical state
+
+
Possible modes are currently:
+
- "PD": Pressure, Density
+
- "PH": Pressure, Enthalpy
+
- "PQ": Pressure, Quality
+
- "PS": Pressure, Entropy
+
- "PT": Pressure, Temperature
+
- "PU": Pressure, Internal Energy
+
- "TD": Temperature, Density
+
- "TH": Temperature, Enthalpy
+
- "TQ": Temperature, Quality
+
- "TS": Temperature, Entropy
+
- "TU": Temperature, Internal Energy
+
- "DH": Density, Enthalpy
+
- "DS": Density, Entropy
+
- "DU": Density, Internal Energy
+
- "HS": Enthalpy, Entropy
+
+
Parameters:
+
-----------
+
:param string mode:
+
Defines which input state variables are given (see possible modes above)
+
:param float var1:
+
Value of state variable 1 (first one in name) - use SI units!
+
:param float var2:
+
Value of state variable 2 (second one in name) - use SI units!
+
:param int kr:
+
phase flag (kr=1: lower density, kr=2: higher density)
+
relevant for "TH", "TS", "TU"
+
+
Return:
+
-------
+
:return ThermodynamicState state:
+
Thermodynamic state with state variables
+
"""
+
# Multiplier for pressure since kPa is used in RefProp
+
p_multi = 1e-3
+
# Multiplier for energy
+
e_multi = self.M
+
# Multiplier for density
+
d_multi = 1 / self.M / 1000
+
+
# Init all parameters
+
p = None
+
d = None
+
T = None
+
u = None
+
h = None
+
s = None
+
q = None
+
+
# Check modi
+
+
# Pressure and density
+
if mode == "PD":
+
p = var1
+
d = var2
+
var1 = var1 * p_multi
+
var2 = var2 * d_multi
+
tmp = self.rp.PDFLSHdll(var1, var2, self._mol_frac)
+
# Pressure and enthalpy
+
elif mode == "PH":
+
p = var1
+
var1 = var1 * p_multi
+
h = var2
+
var2 = var2 * e_multi
+
tmp = self.rp.PHFLSHdll(var1, var2, self._mol_frac)
+
# Pressure and quality
+
elif mode == "PQ":
+
p = var1
+
var1 = var1 * p_multi
+
q = var2
+
# In case current fluid is mixture you need to transform Q to molar base for RefProp-function
+
if self._mix_flag:
+
var2 = self._call_refprop("PQMASS", "QMOLE", p, q, i_mass=1).Output[0]
+
tmp = self.rp.PQFLSHdll(var1, var2, self._mol_frac, 0)
+
# Pressure and entropy
+
elif mode == "PS":
+
p = var1
+
var1 = var1 * p_multi
+
s = var2
+
var2 = var2 * e_multi
+
tmp = self.rp.PSFLSHdll(var1, var2, self._mol_frac)
+
# Pressure and Temperature
+
elif mode == "PT":
+
p = var1
+
var1 = var1 * p_multi
+
T = var2
+
tmp = self.rp.TPFLSHdll(var2, var1, self._mol_frac)
+
# Pressure and internal energy
+
elif mode == "PU":
+
p = var1
+
var1 = var1 * p_multi
+
u = var2
+
var2 = var2 * e_multi
+
# mode = "PE"
+
tmp = self.rp.PEFLSHdll(var1, var2, self._mol_frac)
+
# Temperature and density
+
elif mode == "TD":
+
T = var1
+
d = var2
+
var2 = var2 * d_multi
+
tmp = self.rp.TDFLSHdll(var1, var2, self._mol_frac)
+
# Temperature and enthalpy
+
elif mode == "TH":
+
T = var1
+
h = var2
+
var2 = var2 * e_multi
+
tmp = self.rp.THFLSHdll(T, var2, self._mol_frac, kr)
+
# Temperature and quality
+
elif mode == "TQ":
+
T = var1
+
q = var2
+
# In case current fluid is mixture you need to transform Q to molar base for RefProp-function
+
if self._mix_flag:
+
var2 = self._call_refprop("TQMASS", "QMOLE", T, q, i_mass=1).Output[0]
+
tmp = self.rp.TQFLSHdll(T, var2, self._mol_frac, 1)
+
# Temperature and entropy
+
elif mode == "TS":
+
T = var1
+
s = var2
+
var2 = var2 * e_multi
+
tmp = self.rp.TSFLSHdll(T, var2, self._mol_frac, kr)
+
# Temperature and internal energy
+
elif mode == "TU":
+
T = var1
+
u = var2
+
var2 = var2 * e_multi
+
# mode = "TE"
+
tmp = self.rp.TEFLSHdll(T, var2, self._mol_frac, kr)
+
# Density and enthalpy
+
elif mode == "DH":
+
d = var1
+
var1 = var1 * d_multi
+
h = var2
+
var2 = var2 * e_multi
+
tmp = self.rp.DHFLSHdll(var1, var2, self._mol_frac)
+
# Density and entropy
+
elif mode == "DS":
+
d = var1
+
var1 = var1 * d_multi
+
s = var2
+
var2 = var2 * e_multi
+
tmp = self.rp.DSFLSHdll(var1, var2, self._mol_frac)
+
# Density and inner energy
+
elif mode == "DU":
+
d = var1
+
var1 = var1 * d_multi
+
u = var2
+
var2 = var2 * e_multi
+
# mode = "DE"
+
tmp = self.rp.DEFLSHdll(var1, var2, self._mol_frac)
+
elif mode == "HS":
+
h = var1
+
var1 = var1 * e_multi
+
s = var2
+
var2 = var2 * e_multi
+
tmp = self.rp.HSFLSHdll(var1, var2, self._mol_frac)
+
else:
+
raise ValueError("Chosen mode is not available in refprop calc_state function!")
+
+
# Check for errors
+
self._check_error(tmp.ierr, tmp.herr, self.calc_state.__name__)
+
+
# Get all state variables
+
if p is None:
+
p = tmp.P / p_multi
+
if T is None:
+
T = tmp.T
+
if u is None:
+
u = tmp.e / e_multi
+
if h is None:
+
h = tmp.h / e_multi
+
if s is None:
+
s = tmp.s / e_multi
+
if d is None:
+
d = tmp.D / d_multi
+
if q is None:
+
if self._mix_flag:
+
# Transform current q (molar) to mass based quality
+
tmp2 = self._call_refprop("PH", "QMASS", p, h * e_multi, i_mass=1)
+
if tmp2.Output[0] < 0:
+
q_mass = tmp2.ierr
+
else:
+
q_mass = tmp2.Output[0]
+
q = q_mass
+
else:
+
q = tmp.q
+
+
# # In case not in two phase region reset q to -1
+
# if q > 1 or q < 0:
+
# q = -1
+
+
# Define state
+
state = ThermodynamicState(p=p, T=T, u=u, h=h, s=s, d=d, q=q)
+
return state
+
+
[docs] def calc_satliq_state(self, s):
+
"""s in kJ/kgK"""
+
s = s * self.M * 1000 # kJ/kgK -> J/molK
+
tmp = self.rp.SATSdll(s=s, z="", kph=1)
+
self._check_error(tmp.ierr, tmp.herr, self.calc_satliq_state.__name__)
+
if tmp.k1 != 1:
+
raise TypeError
+
p = tmp.P1 * 1000 # kPa -> Pa
+
d = tmp.D1 * self.M * 1000 # mol/l -> kg/mol
+
return self.calc_state("PD", p, d)
+
+
[docs] def calc_transport_properties(self, state: ThermodynamicState):
+
""" Calculate transport properties of RefProp fluid at given state
+
+
Parameters:
+
-----------
+
:param ThermodynamicState state:
+
Current thermodynamic state
+
Return:
+
-------
+
:return TransportProperties props:
+
Instance of TransportProperties
+
"""
+
# Get properties
+
tmp = self._call_refprop_allprop("PRANDTL,VIS,TCX,KV,CV,CP,BETA,STN,ACF", state.T, state.d / self.M, i_mass=0)
+
# Create transport properties instance
+
props = TransportProperties(lam=tmp.Output[2],
+
dyn_vis=tmp.Output[1],
+
kin_vis=tmp.Output[3],
+
pr=tmp.Output[0],
+
cp=tmp.Output[5] / self.M,
+
cv=tmp.Output[4] / self.M,
+
beta=tmp.Output[6],
+
sur_ten=tmp.Output[7],
+
ace_fac=tmp.Output[8],
+
state=state)
+
# Return props
+
return props
+
+
[docs] def get_available_substances(self,
+
mode="all",
+
include_ending=False,
+
save_txt=False):
+
""" Get all available RefProp fluids (mixtures and / or pure substances depending on mode)
+
+
Parameters:
+
-----------
+
:param string mode:
+
Mode defining which kind of fluids you want to have: 'pure': pure substances, 'mix': mixtures, 'all': all
+
:param boolean include_ending:
+
Defines, whether file ending shall be returned as well or not
+
:param boolean save_txt:
+
Defines, whether a text file with names shall be created in current working directory
+
Return:
+
-------
+
:return list names:
+
String list containing names of available fluids (depending on defined mode)
+
"""
+
# Possible endings
+
_endings = ["MIX", "FLD", "PPF"]
+
+
# Folders where fluid data is located
+
folders = [r"FLUIDS", r"MIXTURES"]
+
+
# Define paths by mode
+
if mode == "pure":
+
paths = [os.path.join(self._ref_prop_path, folders[0])]
+
elif mode == "mix":
+
paths = [os.path.join(self._ref_prop_path, folders[1])]
+
elif mode == "all":
+
paths = [os.path.join(self._ref_prop_path, folders[0]),
+
os.path.join(self._ref_prop_path, folders[1])]
+
else:
+
raise ValueError("Chosen mode '{}' is not possible!".format(mode))
+
+
# Get files in folders, remove ending and append to names
+
names = []
+
for p in paths:
+
# Get all files in directory
+
files = [f for f in os.listdir(p) if os.path.isfile(os.path.join(p, f))]
+
# Remove ending
+
if include_ending:
+
tmp = [path for path in files if path.split(".")[1] in _endings]
+
else:
+
tmp = [path.split(".")[0] for path in files if path.split(".")[1] in _endings]
+
# Append names to names list
+
names.extend(tmp)
+
+
# In case names shall be stored
+
if save_txt:
+
with open("available_fluids.txt", "w") as output:
+
for i in names:
+
output.write("{} \n".format(i))
+
+
if not names:
+
raise ValueError("No fluids are in current RefProp directory. Check path '{}'!".format(self._ref_prop_path))
+
+
# Return names
+
return names
+
+
[docs] def get_comp_names(self):
+
""" Get name of components within current fluid"
+
+
Return:
+
-------
+
:return list comp_names:
+
String names of components within current fluid
+
"""
+
return self._comp_names
+
+
[docs] def get_nbp(self):
+
""" Get normal boiling point (T @ q=0 and 1 bar)
+
+
Return:
+
-------
+
:return float nbp:
+
Normal boiling point of refrigerant in °C
+
"""
+
if not self._nbp:
+
self._nbp = self.calc_state("PQ", 1e5, 0.0).T - 273.15
+
+
return self._nbp
+
+
[docs] def get_molar_composition(self, state: ThermodynamicState, z_molar=None):
+
""" Get composition on molar base. Liquid phase, vapor phase and total.
+
+
:param ThermodynamicState state: the state whose compositions will be returned
+
:param list z_molar: molar composition of fluid. In case of None, default value _mol_frac is used
+
:return:
+
- list x:
+
composition of liquid phase
+
- list y:
+
composition of vapor phase
+
- list z:
+
composition in total
+
"""
+
if z_molar is None:
+
z = self._mol_frac
+
M = self.M
+
else:
+
z = z_molar
+
M = self.rp.REFPROPdll(hFld="",
+
hIn="",
+
hOut="M",
+
iUnits=self.molar_base_si,
+
iMass=0,
+
iFlag=1,
+
a=0,
+
b=0,
+
z=z_molar).Output[0]
+
num_components = len(z)
+
+
tmp = self.rp.TDFLSHdll(T=state.T,
+
D=state.d / M / 1000,
+
z=z)
+
# TDFLSHdll is deprecated, use the following in future:
+
# tmp = self.rp.ABFLSHdll(ab="TD",
+
# a=state.T,
+
# b=state.d / self.M / 1000,
+
# z=self._mol_frac,
+
# iFlag=0) # molar units
+
+
x = list(tmp.x[:num_components])
+
y = list(tmp.y[:num_components])
+
+
return x, y, z
+
+
[docs] def get_critical_point(self):
+
""" Get T and p of critical point
+
+
Return:
+
-------
+
:return float Tc:
+
Temperature at critical point in K
+
:return float pc:
+
Pressure at critical point in Pa
+
:return float dc:
+
Density at critical point in kg/m^3
+
"""
+
mode = 2
+
if mode == 1:
+
tmp = self._call_refprop("CRIT", "T,P,D", i_mass=1)
+
Tc = tmp.Output[0]
+
pc = tmp.Output[1]
+
dc = tmp.Output[2] * self.M
+
else:
+
res = self._call_refprop_allprop("TC,PC,DC")
+
Tc = res.Output[0]
+
pc = res.Output[1]
+
dc = res.Output[2] * self.M
+
return Tc, pc, dc
+
+
#
+
#
+
[docs] def get_def_limits(self):
+
""" Get limits of current ref prop fluid
+
Limits contain Tmin, Tmax, Dmax and Pmax (Temperatures, density, pressure)
+
+
:return dict limits:
+
Dictionary with definition limits in RefProp. Contains min/max temperature, max density, max pressure.
+
"""
+
tmp = self._call_refprop_allprop("TMIN,TMAX,DMAX,PMAX")
+
limits = {"Tmin": tmp.Output[0],
+
"Tmax": tmp.Output[1],
+
"Dmax": tmp.Output[2] * self.M,
+
"Pmax": tmp.Output[3]}
+
return limits
+
+
[docs] @staticmethod
+
def get_dll_path(ref_prop_path: str):
+
"""
+
Return the location of the dll
+
+
Return:
+
:return: string path_to_dll:
+
Path of a valid dll
+
"""
+
path_to_dll = os.path.join(ref_prop_path, r"REFPRP64.DLL")
+
+
# Check if dll actually exists:
+
if not os.path.exists(path_to_dll):
+
raise FileNotFoundError("Selected dll not found automatically. "
+
"Please alter the local attribute or search for yourself.")
+
+
return path_to_dll
+
+
[docs] def get_fluid_name(self):
+
""" Get fluid name.
+
+
Return:
+
-------
+
:return string fluid_name:
+
Fluid name
+
"""
+
return self.fluid_name
+
+
[docs] def get_gwp(self):
+
""" Get gwp of current fluid from refProp
+
+
Return:
+
-------
+
:return float gwp:
+
Global warming potential of fluid. In case calculation failed, None will be returned.
+
"""
+
# Call refProp
+
tmp = self._call_refprop("",
+
"GWP",
+
i_mass=1)
+
# Calculate gwp
+
gwp = round(sum(max(tmp.Output[i], 0) * self._mass_frac[i] for i in range(self._n_comp)), 2)
+
# In case GWP cannot be calculated
+
if gwp < 0:
+
gwp = 0
+
return gwp
+
+
[docs] def get_longname(self):
+
""" Get longname of fluid
+
+
Return:
+
-------
+
:return string longname:
+
Name of current fluid in refprop - provides mass fractions and components as well (in case of mixture)
+
"""
+
longname = self._call_refprop("", "LONGNAME(0)").hUnits
+
return longname
+
+
[docs] def get_mass_fraction(self, use_round=True):
+
""" Get mass fractions of pure substances in current fluid
+
+
Parameters:
+
:param boolean use_round:
+
Flag to define, whether the exact values or rounded values (by the fourth number) shall be used
+
Return:
+
-------
+
:return list mass_frac:
+
List of component mass fractions
+
"""
+
if use_round:
+
mass_frac = [round(i, 4) for i in self._mass_frac]
+
else:
+
mass_frac = self._mass_frac
+
return mass_frac
+
+
[docs] def get_molar_mass(self):
+
""" Get molar mass of current fluid
+
+
Return:
+
-------
+
:return float M:
+
Molar mass of current fluid in kg/mol
+
"""
+
return self.M
+
+
[docs] def get_mol_fraction(self, use_round=True):
+
""" Get mol fractions of pure substances in current fluid
+
+
Parameters:
+
:param boolean use_round:
+
Flag to define, whether the exact values or rounded values (by the fourth number) shall be used
+
Return:
+
-------
+
:return list frac:
+
List of component mol fractions
+
"""
+
if use_round:
+
mol_frac = [round(i, 4) for i in self._mol_frac]
+
else:
+
mol_frac = self._mol_frac
+
return mol_frac
+
+
[docs] def get_odp(self):
+
""" Calculate ozone depletion potential
+
In case of mixtures: Maximum value of pure substances will be used
+
+
Return:
+
-------
+
:return float odp:
+
ODP of fluid. In case calculation failed, None will be returned.
+
"""
+
# Call refProp
+
tmp = self._call_refprop("",
+
"ODP",
+
i_mass=1)
+
# Calculate odp
+
odp = max(max(tmp.Output), 0)
+
# In case some error occured
+
if odp < 0:
+
odp = 0
+
return odp
+
+
[docs] def get_safety(self):
+
""" Calculate safety class of refrigerant
+
+
Return:
+
-------
+
:return string safety:
+
Safety class of fluid.
+
"""
+
# Call refProp
+
tmp = self._call_refprop("",
+
"SAFETY",
+
i_mass=1)
+
# Get safety class
+
safety = tmp.hUnits
+
# Return safety
+
return safety
+
+
[docs] def get_sat_vap_pressure(self, T_sat):
+
""" Get pressure of saturated vapor for defined temperature
+
+
Note:
+
- works for vapor, liquid and solid
+
- returns equilibrium pressure at defined line (q=1)
+
+
Parameters:
+
:param float T_sat:
+
Temperature in K
+
Return:
+
:return float p_sat:
+
Vapor pressure in Pa
+
"""
+
trip = self.get_triple_point()
+
if trip[0] <= T_sat:
+
p_sat = self.calc_state("TQ", T_sat, 1.0).p
+
else:
+
tmp = self.rp.REFPROPdll("", "TSUBL", "P", self.molar_base_si, 0, 0, T_sat, 0.0, self._mol_frac)
+
p_sat = tmp.Output[0]
+
return p_sat
+
+
[docs] def get_triple_point(self):
+
""" Get temperature and pressure at triple point of current fluid
+
+
Note: Works fine for pure substances, mixtures might not work properly
+
+
Return:
+
:return float T_tpl:
+
Temperature at triple point in K
+
:return float p_tpl:
+
Pressure at triple point in Pa
+
"""
+
# Call Refprop
+
tmp = self._call_refprop("TRIP", "T;P")
+
return tmp.Output[0], tmp.Output[1]
+
+
[docs] def get_version(self):
+
""" Get version of wrapper and used RefProp dll
+
+
Return:
+
:return string wrapper_version:
+
Refprop wrapper version
+
:return string refprop_version:
+
Version of used RefProp dll
+
"""
+
return self.__version__, self.rp.RPVersion()
+
+
[docs] def is_mixture(self):
+
""" Find out if fluid is mixture or not.
+
In case current fluid is mixture, true is returned.
+
In case current fluid is pure substance, false is returned.
+
+
Return:
+
-------
+
:return boolean _mix_flag:
+
Boolean for mixture (True), pure substance (False)
+
"""
+
return self._mix_flag
+
+
[docs] def set_error_flag(self, flag):
+
""" Set error flag
+
+
Parameters:
+
:param boolean flag:
+
New error flag
+
Return:
+
:return int err:
+
Notifier for error code - in case everything went fine, 0 is returned
+
"""
+
self._flag_check_errors = flag
+
return 0
+
+
[docs] def set_warning_flag(self, flag):
+
""" Set warning flag
+
+
Parameters:
+
:param boolean flag:
+
New warning flag
+
Return:
+
:return int err:
+
Notifier for error code - in case everything went fine, 0 is returned
+
"""
+
self._flag_warnings = flag
+
return 0
+