# -*- coding: utf-8 -*-
"""
Created on Mon May 22 21:09:12 2023
@author: atakan
"""
import os
import copy
import ctREFPROP.ctREFPROP as ct
from ctREFPROP.ctREFPROP import REFPROPFunctionLibrary
import numpy as np
import carbatpy
from dataclasses import dataclass, field
from typing import Optional, List
[docs]
DEFAULTS = carbatpy.CB_DEFAULTS
[docs]
TREND = DEFAULTS['Fluid_Defaults']['TREND']
# verify installation
if TREND["TREND_INSTALLED"]:
try:
import TRENDfluid.fluid as tr_fl # TREND fluids
except ImportError as e:
print(f"Import error for 'TRENDfluid.fluid': {e}")
_PROPS = DEFAULTS["Fluid_Defaults"]['PROPS'] # "REFPROP" # or "CoolProp"
_fl_properties_names = DEFAULTS["Fluid_Defaults"]['Property_Names']
_fl_properties_names_trend = DEFAULTS["Fluid_Defaults"]['Property_Names_Trend']
_THERMO_STRING = DEFAULTS["Fluid_Defaults"]['THERMO_STRING']
# careful density not volume
_THERMO_TREND = request = DEFAULTS["Fluid_Defaults"]['THERMO_TREND']
_TRANS_STRING = DEFAULTS["Fluid_Defaults"]['TRANS_STRING']
_TRANS_TREND = DEFAULTS["Fluid_Defaults"]['TRANS_TREND']
_TRANS_TREND_MIX = DEFAULTS["Fluid_Defaults"]['TRANS_TREND_MIX']
@dataclass
[docs]
class FluidModelDefaults:
[docs]
props = DEFAULTS["Fluid_Defaults"]['PROPS']
# TREND
[docs]
eos_ind: Optional[List[int]] = None
[docs]
eos_ind_default: int = 1
[docs]
path = carbatpy.CB_DEFAULTS['Fluid_Defaults']['TREND']["TREND_PATH"]
[docs]
dll_path = carbatpy.CB_DEFAULTS['Fluid_Defaults']['TREND']["TREND_DLL"]
# CUSTOM
[docs]
class FluidModel:
"""
Fluid model to be used Refprop or TREND
Only model, chemical compunds units and for TREND some more values are set here. The
mixture composition, state etc. is set in Fluid.
"""
def __init__(self, fluid, composition=[1], **kwargs):
"""
For Class Fluidmodell a fluid (mixture) must be defined, the evaluation
takes place with props, units can be set and an instance can be set,
the latter is important, if more than one fluid is used.
For Refprop: Initiates new instance for each fluid (mixture).
Parameters
----------
fluid : String
as defined in the props (Model), for RefProp it is
"fluid1 * fluid2 * fluid3".
composition: array
of working fluid (mixture), at this level only used for TREND
**kwargs : dict
units, props, rp_inst, eos_ind, mix_ind, trendmodel, props_func
(see dataclass FluidModelDefaults). For props="CUSTOM", props_func must be
a callable with signature::
func(values, given) -> list[T, P, H, V, S, Q, U, ...]
returning SI values (K, Pa, J/kg, m³/kg, J/kg/K, -, J/kg).
Returns
-------
None.
"""
cfg = FluidModelDefaults()
for k, v in kwargs.items():
if not hasattr(cfg, k):
raise TypeError(f"Unknown keyword '{k}' for FluidModel")
setattr(cfg, k, v)
if self.props == "TREND":
self.comp = composition
if cfg.eos_ind is None:
self.eos_ind = [cfg.eos_ind_default] * len(self.comp)
else:
self.eos_ind = list(cfg.eos_ind) # Kopie
self.mix_ind = cfg.mix_ind
self.trendmodel = cfg.trendmodel
self.trend_path = cfg.path
self.dll_path = cfg.dll_path
if self.props == "REFPROP":
if cfg.rp_inst == False:
self._init_default_rp_instance()
elif cfg.rp_inst == True:
self._new_rp_instance()
else:
raise ValueError("Unknown instance flag. Chose 0 or 1.")
elif self.props == "TREND":
self._set_tr_fluid()
elif self.props == "CUSTOM":
if cfg.props_func is None:
raise ValueError("For props='CUSTOM', a callable 'props_func' must be provided.")
self.props_func = cfg.props_func
def _init_default_rp_instance(self):
if "RPPREFIX" not in os.environ:
raise EnvironmentError("Environment variable 'RPPREFIX' is not set.")
self.fl_instance = REFPROPFunctionLibrary(os.environ['RPPREFIX'])
self.fl_instance.SETPATHdll(os.environ['RPPREFIX'])
ierr = self.fl_instance.SETFLUIDSdll(self.fluid)
if ierr != 0:
print(f"Error in setfluid {ierr}")
print(self.fl_instance.ERRMSGdll(ierr))
def _new_rp_instance(self):
"""
A new instance of Refpropdll for the given fluid. Requires installation of REFPROPLibraryManager.
REFPROPLibraryManager enables threadsafe handling of different fluids.
More information: https://github.com/usnistgov/REFPROP-manager
"""
try:
manager = ct.REFPROPLibraryManager(os.environ["REFPROPLibraryManager"])
except:
raise ValueError("REFPROPLibraryManager could not be imported.")
self.fl_instance = manager.get_instance(os.environ['RPPREFIX'], "REFPRP64.dll")
if self.fl_instance.managed_handle >= 100:
raise Warning(">100th Refprop instance has been created. Working memory overflow expected shortly.")
self.fl_instance.SETPATHdll(os.environ['RPPREFIX'])
ierr = self.fl_instance.SETFLUIDSdll(self.fluid)
if ierr != 0:
print(f"Fehler in setfluid {ierr}")
print(self.fl_instance.ERRMSGdll(ierr))
def _set_tr_fluid(self):
""" Set the state of a fluid in TREND. """
self.fluid = self.fluid_to_list()
if self.units == 21:
self.units = "specific"
self._trend_sanity_check()
trend_path_incl_subpath = self.trend_path + self.trendmodel
_trend_dict = {"Input": 'HP', # just for initialization
'calctype': 'S', # just for initialization
'fluids': self.fluid,
"moles": self.comp,
"eos_ind": self.eos_ind,
'mix_ind': self.mix_ind,
'path': trend_path_incl_subpath,
'unit': self.units,
'dll_path': self.dll_path,
'libhandle': None}
self.fl_instance = tr_fl.Fluid(*_trend_dict.values())
err_flag = self.fl_instance.errorflag.value
if err_flag > 0:
print(f"fluid-Trend problem, errorflag:{err_flag}")
return self.fl_instance
def _trend_sanity_check(self):
"""
checks if length of composition matches fluid string and length of eos_ind
"""
n_compounds = len(self.fluid)
if len(self.comp) != n_compounds:
raise ValueError(
f"Composition array wrong length, must be {n_compounds}, got {len(self.comp)}"
)
if len(self.eos_ind) != n_compounds:
raise ValueError(
f"Eos_ind array wrong length, must be {n_compounds}, got {len(self.eos_ind)}"
)
if self.trendmodel != "":
if 1 not in self.eos_ind:
raise ValueError(f"Verify TREND inputs: eos_ind {self.eos_ind} and trendmodel {self.trendmodel} both set.")
if self.mix_ind != 1:
raise ValueError(f"Verify TREND inputs: mix_ind {self.mix_ind} and trendmodel {self.trendmodel} both set.")
[docs]
def fluid_to_list(self):
"""
Converts the fluid names string from Refprop to a list for TREND
Returns
-------
List of strings
the fluid names in a list.
"""
no_blank = self.fluid.replace(" ", "")
return no_blank.split("*")
[docs]
class FluidState:
"""
The thermodynamic state of a fluid is stored here.
Which properties (T,p,h,v,s,q,...), depends on:
carbatpy.CB_DEFAULTS["Fluid_Defaults"]["Property_Names_Short"]
"""
def __init__(self, state, what, **kwargs):
[docs]
self.props = kwargs.get("props", _PROPS)
[docs]
self. verbose = kwargs.get("verbose", False)
[docs]
self.liq_x = kwargs.get("x", None)
[docs]
self.vap_y = kwargs.get("y", None)
[docs]
self.total_z = kwargs.get("z", None)
if self.props == "REFPROP":
self.no_val = what.count(";")+1
elif self.props in ("TREND", "CUSTOM"):
self.no_val = len(state)
if self.props in ('REFPROP', 'CUSTOM'):
names = carbatpy.CB_DEFAULTS["Fluid_Defaults"]["Property_Names_Short"]
elif self.props == 'TREND':
names = carbatpy.CB_DEFAULTS["Fluid_Defaults"]["Property_Names_Short_Trend"]
[docs]
self.state = state[:self.no_val]
for ii, key in enumerate(names.keys()):
if ii < self.no_val:
setattr(self,key , state[ii])
[docs]
def state_to_dict(self):
"""Convert the state properties to a dictionary name:value."""
if self.props in ('REFPROP', 'CUSTOM'):
return_dict = dict(
zip(_fl_properties_names[:self.no_val], self.state[:self.no_val]))
elif self.props == 'TREND':
return_dict = dict(
zip(_fl_properties_names_trend[:self.no_val], self.state[:self.no_val]))
return_dict["x"] = self.liq_x
return_dict["y"] = self.vap_y
return_dict["z"] = self.total_z
return return_dict
[docs]
class Fluid:
"""
The Fluid class is used to set, get, and print states of a fluid with a given
model (e.g. RefProp). The compounds are set in the fluidmodel, while the
composition is also set here. The calculated values depend also on
carbatpy.CB_DEFAULTS["Fluid_Defaults"]["Property_Names_Short"].
"""
def __init__(self, fluidmodel, composition=None, option=1):
"""
Generate a Fluid instance
a FluidModel instance, the composition and an unused option has to be passed.
Parameters
----------
fluidmodel : FluidModel
the instance of the property model to be used.
composition : list, optional
mole fractions of all compounds. The default is [1.0].
option : TYPE, optional
unused. The default is 1.
Returns
-------
None.
"""
[docs]
self.fluidmodel = fluidmodel
if composition is None:
composition = [1.0]
[docs]
self.composition = copy.copy(composition)
[docs]
self.no_compounds = len(composition)
[docs]
self.comp_mass = np.zeros(self.no_compounds)
[docs]
self.props = fluidmodel.props
# BA something is wrong here (probably 2024-07-10
self.set_composition(composition)
[docs]
def set_composition(self, composition, **kwargs):
"""
Mole fractions are set and mass fractions are calculated.
Sets the self.composition used in the Fluid settings and the self.comp_mass
TREND needs the mass fractions for specific (/kg) calculations. Thus
calc_mass_fraction() is called.
Parameters
----------
composition : list/numpy.array
mole fractions must sum up to 1.
kwargs : dictionary, optional.
- verbose : boolean, optional. Generate additional printig. The default is
False.
Returns
-------
None.
"""
verbose = kwargs.get("verbose", False)
if verbose:
print("set_comp", composition, self.composition, self.comp_mass)
self.composition = composition
if self.fluidmodel.props == "TREND":
self.comp_mass = np.zeros((self.no_compounds))
self.calc_mass_fraction()
self.fluidmodel.fl_instance.set_moles(self.comp_mass)
# here is the mass fraction to internal mol fraction conversion
elif self.fluidmodel.props == "REFPROP":
self.comp_mass = self._rp_mass_from_mole(composition)
if verbose:
print("set_comp-after", composition, self.composition, self.comp_mass)
[docs]
def calc_mass_fraction(self):
""" mole to mass fractions for TREND"""
mass_frac = np.zeros(self.no_compounds)
for n_c in range(self.no_compounds):
mass_frac[n_c] = self.fluidmodel.fl_instance.get_mw(n_c) * \
self.composition[n_c]
self.comp_mass = mass_frac/mass_frac.sum()
return self.comp_mass
[docs]
def calc_mole_fraction(self, comp_mass):
"""
needed for x_liq and y_vap in TREND, therefore mass fraction passed as function argument.
"""
mole_frac = np.zeros(self.no_compounds)
for n_c in range(self.no_compounds):
mole_frac[n_c] = comp_mass[n_c] / self.fluidmodel.fl_instance.get_mw(n_c)
comp_mole = mole_frac/mole_frac.sum()
return comp_mole
[docs]
def set_state(self, values, given="TP", wanted=_THERMO_STRING, composition=None,
**kwargs):
"""
Sets the state of a fluid and calculates the wanted properties.
Parameters
----------
values : list[2]
the two state parameter values.
given : String, optional
What are the two values (T,P,H,S,Q,V). The default is "TP".
wanted : String, optional
Which parameters shall be calculated? The default is _THERMO_STRING.
composition : list, optional
mole fraction of each compound, must sum up to 1. When empty, the last/actual
value will be used. The default is [].
kwargs : dictionary, optional
dll_select : str, optional
is passed to Refprop. The standard value is "2dll" using the
faster REFPROP2dll function, not calculating the composition of different
phases. The alternative "dll" is slower but does it. Throughout it is
expected that compoitions are mole fractions and qualities in mass/mass.
i_mass : int (0/1), optional
should not be used, not tested.
i_flag : int (0/1), optional
should not be used, not tested.
verbose : Boolean, optional
leads to additional (printing) output. The default value is False.
output : String, optional
what shall be the return value, the "state" as list or an instance
of the "FluidState". default is "state"
Raises
------
Exception
DESCRIPTION.
Returns
-------
TYPE
DESCRIPTION.
"""
composition = self._update_composition(composition)
values = [_scalar(values[0]), _scalar(values[1])]
self. output =kwargs.get("output", "state")
if self.fluidmodel.props == "REFPROP":
self._set_state_refprop(values, given, wanted, **kwargs)
elif self.fluidmodel.props == "TREND":
if wanted == _THERMO_STRING:
wanted = _THERMO_TREND
self._set_state_trend(values, given, wanted, **kwargs)
elif self.fluidmodel.props == "CUSTOM":
self._set_state_custom(values, given, wanted, **kwargs)
else:
raise NotImplementedError(
f"Property model {self.fluidmodel.props} not implemented yet!")
if self.output =="FluidState":
return self.properties
return np.array([*self.properties.state])
def _update_composition(self, composition):
if composition is None: # mole fractions only!
if self.props =='TREND':
return self.comp_mass
else:
return self.composition
self.set_composition(composition) # important for trend mass->mole
if VERBOSE:
print("comp-set", composition, self.composition,
self.comp_mass, self.composition)
return composition
def _set_state_refprop(self, values, given, wanted, **kwargs):
i_mass = kwargs.get("iMass", 0)
i_flag = kwargs.get("iFlag", 0) # if this is 1 most tests fail!
dll_select = kwargs.get("dll_select", "2dll") # "2dll"
verbose = kwargs.get("verbose", False)
if given.count("Q") > 0:
q_act = values[given.find("Q")]
if 0 < q_act < 1: # only then x and y are meaningful/needed
dll_select = "dll"
if self.fluidmodel.fl_instance.managed_handle == None:
# single REFPROP instance, thus fluid string needs to be forwarded
fluid = self.fluidmodel.fluid
else:
# properly set instance is used, thus faster calls by omitting fluid string evaluation
fluid = ""
match dll_select: # noqa
case "2dll":
i_mass = 0
state = self.fluidmodel.fl_instance.REFPROP2dll(
fluid, given, wanted,
self.fluidmodel.units,
i_flag, values[0], values[1],
copy.copy(self.composition)
)
# additional calls implemented that are used if flash calculation failed (error code 226)
# which may happen close to saturation lines. flag 1 is single phase assumption, flag 2 is two-phase assumption
if state.ierr == 226:
state = self.fluidmodel.fl_instance.REFPROP2dll(
fluid, given, wanted,
self.fluidmodel.units,
2, values[0], values[1],
copy.copy(self.composition)
)
if verbose:
print("iflag 2 called")
if state.ierr == 226:
state = self.fluidmodel.fl_instance.REFPROP2dll(
fluid, given, wanted,
self.fluidmodel.units,
1, values[0], values[1],
copy.copy(self.composition)
)
if verbose:
print("iflag 1 called")
case "dll":
# super fishy, imass usage in REFPROPdll from REFPROPFunctionLibrary most likely erroneous
# imass = 0 is supposed to use composition and quality on molar basis and does so
# imass = 1 is supposed to use composition and quality on mass basis, but uses composition on molar basis!
# really messy: REFPROPLibraryManager seems to use imass correctly
i_mass = 1
if self.fluidmodel.fl_instance.managed_handle != None:
to_use_composition = copy.copy(self.comp_mass)
else:
to_use_composition = copy.copy(self.composition)
state = self.fluidmodel.fl_instance.REFPROPdll(
fluid, given, wanted,
self.fluidmodel.units,
i_mass,
i_flag, values[0], values[1],
to_use_composition
)
if verbose:
print(state.z[:5], self.composition, self.comp_mass)
case _:
raise ValueError(f"This iMass value is not allowed {i_mass}")
if state.ierr == 0: # for estimated mixing parameters, add: or state.ierr == -117:
state_val = state.Output
z_act = state.z[:self.no_compounds]
x_act = y_act = None
if state.q > 0 and state.q < 1: # mole -> mass quality problem of Refprop
# state_val[5] = state.q
if dll_select == "dll":
x_act = state.x[:self.no_compounds]
y_act = state.y[:self.no_compounds]
if self.fluidmodel.units == 21: # convert back to mole fractions
x_act = np.array(self._rp_mole_from_mass(state.x))
y_act = np.array(self._rp_mole_from_mass(state.y))
z_act = np.array(self._rp_mole_from_mass(state.z))
self.properties = FluidState(state_val, wanted, x=x_act, y=y_act, z=z_act)
self.val_dict = self.properties.state_to_dict()
else:
self.herr = state.herr
raise NotImplementedError(f"Property-Refprop problem: {state.herr}, {state.ierr}!")
def _rp_mass_from_mole(self, composition):
return self.fluidmodel.fl_instance.XMASSdll(composition).xkg[:self.no_compounds]
def _rp_mole_from_mass(self, composition):
return self.fluidmodel.fl_instance.XMOLEdll(composition).xmol[:self.no_compounds]
def _set_state_trend(self, values, given, wanted, **kwargs):
verbose = kwargs.get("verbose", False)
self.fluidmodel.fl_instance.set_moles(self.comp_mass)
if verbose:
print("in TREND set state")
values, given = self._adjust_trend_values(values, given)
all_values = self.fluidmodel.fl_instance.ALLPROP(given, values[0], values[1])
all_val_initial = copy.copy(all_values)
phase_flag = (self.no_compounds > 1 and 1 > all_values["QEOS"] > 0)
if phase_flag:
self.fluidmodel.fl_instance.set_moles(self.comp_mass)
self.fluidmodel.fl_instance.set_input("TP")
res_flash = self.fluidmodel.fl_instance.FLASH(all_values["T"], all_values["P"])
phasetype = list(res_flash["phasetype"]) # codes: 1=VAP, 2=LIQ1, 3=LIQ2
x_phase = np.array(res_flash["x_phase"])
x_phase = x_phase[:, :self.no_compounds]
ivap = phasetype.index(1)
if 2 in phasetype: #liquid marked either as lighter or heavier liquid phase
iliq = phasetype.index(2)
elif 3 in phasetype:
iliq = 2
vap_y = self.calc_mole_fraction(x_phase[ivap, :])
liq_x = self.calc_mole_fraction(x_phase[iliq, :])
if all_values["QEOS"] > 0: #transfer q_mole to q_mass
all_values["QEOS"] = self.calc_q_mass(all_values["QEOS"],liq_x, vap_y)
if all_values["T"] > 0: # on error it is -8888
all_values["P"] = all_values["P"] * 1e6 # MPa
all_values["D"] = 1 / all_values["D"] # V (spec. volume)
all_values = [all_values[key] for key in _THERMO_TREND]
if len(wanted) > len(_THERMO_TREND):
trans_dict = {}
speed_sound = all_val_initial["WS"]
for tr_want in wanted[len(_THERMO_TREND):]:
self.fluidmodel.fl_instance.set_moles(self.comp_mass)
self.fluidmodel.fl_instance.set_calctype(tr_want)
self.fluidmodel.fl_instance.set_input(given)
trans_dict[tr_want], err = self.fluidmodel.fl_instance.TREND_EOS(
*values)
if verbose:
print(tr_want, ":", trans_dict[tr_want], err)
if trans_dict[tr_want] < 0:
print(f"WARNING: Problem / NOT IMPLEMENTED IN TREND! {tr_want}")
try:
trans_dict['ETA_RES'] = trans_dict['ETA_RES'] * 10**-6 #Pa s
except:
pass
try:
trans_dict['ETA_ECS'] = trans_dict['ETA_ECS'] * 10**-6 #Pa s
except:
pass
try:
trans_dict['ETA_BERLIN'] = trans_dict['ETA_BERLIN'] * 10**-6 #Pa s
except:
pass
try:
trans_dict['ST'] = trans_dict['ST'] / 10 ** 3 #TREND gives back 10 ** 3 N/m
except:
pass
all_values.extend(trans_dict.values())
if phase_flag:
self.properties = FluidState(all_values, wanted, props="TREND", x=liq_x, y=vap_y)
else:
self.properties = FluidState(all_values, wanted, props="TREND")
self.val_dict = self.properties.state_to_dict()
else:
raise NotImplementedError(
f"property calculation in TREND failed, probably not implemented please look up the TREND error code : {all_values['T']}")
def _set_state_custom(self, values, given, wanted=_THERMO_STRING, **kwargs):
"""
Evaluate fluid properties using a user-supplied callable (props="CUSTOM").
The callable stored in self.fluidmodel.props_func is called with the two
input values and the 'given' string (e.g. "TP", "PH") and must return a
list or array of property values in SI units (mass basis) in the order:
[T (K), P (Pa), H (J/kg), V (m³/kg), S (J/kg/K), Q (-), U (J/kg)]
Transport properties may follow optionally in the same order as
CB_DEFAULTS["Fluid_Defaults"]["Property_Names_Short"].
Parameters
----------
values : list[float, float]
The two thermodynamic input values.
given : str
Identifier for the two inputs (e.g. "TP", "PH", "TQ").
wanted : str, optional
Which parameters shall be calculated? The default is _THERMO_STRING.
The callable receives the following additional keyword arguments:
- ``fluid`` : ``self.fluidmodel.fluid`` (str)
- ``composition`` : ``self.composition`` (list)
- ``wanted`` : ``wanted`` (str)
Its signature should be::
func(values, given, fluid=..., composition=..., wanted=...)
"""
state = list(self.fluidmodel.props_func(values, given,
wanted=wanted,
fluid=self.fluidmodel.fluid,
composition=self.composition))
self.properties = FluidState(state, wanted, props="CUSTOM")
self.val_dict = self.properties.state_to_dict()
[docs]
def calc_q_mass(self, q, liq_x, liq_y):
vap_mass_i = np.zeros(self.no_compounds)
liq_mass_i = np.zeros(self.no_compounds)
for n_c in range(self.no_compounds):
vap_mass_i[n_c] = self.fluidmodel.fl_instance.get_mw(n_c) * \
liq_y[n_c]
liq_mass_i[n_c] = self.fluidmodel.fl_instance.get_mw(n_c) * \
liq_x[n_c]
q_mass = q * vap_mass_i.sum() / (q* vap_mass_i.sum() +( 1-q)*liq_mass_i.sum())
return q_mass
def _adjust_trend_values(self, values, given):
pressure_in = given[:2].find("P")
quality_in = given[:2].find("Q")
temp_in = given[:2].find("T")
if pressure_in >= 0:
values[pressure_in] = values[pressure_in] / 1e6 # MPa in Trend
if quality_in > 0:
if pressure_in >= 0:
values, given = self._adjust_values_for_quality_pressure(
values, quality_in, pressure_in)
elif temp_in >= 0:
values, given = self._adjust_values_for_quality_temperature(
values, quality_in, temp_in)
else:
raise NotImplementedError(
f'Error: Qualities cannot be combined with {given}!')
return values, given
def _adjust_values_for_quality_pressure(self, values, quality_in, pressure_in):
match values[quality_in]:
case 0.:
given = "PLIQ"
case 1.:
given = "PVAP"
case _:
raise ValueError('Error: only qualities 0 or 1 for mixtures in TREND!')
values[0] = values[pressure_in]
values[1] = 10
return values, given
def _adjust_values_for_quality_temperature(self, values, quality_in, temp_in):
match values[quality_in]:
case 0.:
given = "TLIQ"
case 1.:
given = "TVAP"
case _:
raise ValueError('Error: only qualities 0 or 1 for mixtures!')
values[0] = values[temp_in]
values[1] = 10
return values, given
[docs]
def set_state_v(self, values, given="TP", wanted=_THERMO_STRING, **kwargs):
"""
set many Fluid states with the values given as array(vector)
Parameters
----------
values : numpy.array((N,2))
the N value pairs to fix the states.
given : String, optional
what are the input values?(T,P, Q, S,...). The default is "TP".
wanted : String, optional
with all the outputs wanted. The default is _THERMO_STRING.
kwargs : dictionary optional
This is optional and passed to REFPROP or TREND, depending on the props
settings / chosen property model. (see set_state())
Returns
-------
output : numpy.array(N, n_wanted)
all the wanted properties for the N inputs.
"""
dimension = np.shape(values)
number_wanted = wanted.count(";")+1
output = np.zeros((dimension[0], number_wanted))
for count, value in enumerate(values):
output[count, :] = self.set_state(value, given, wanted, **kwargs)
self.state_v = output
return output
[docs]
def print_state(self):
""" Print the actual state of the Fluid"""
pr = self.properties
flmod = self.fluidmodel
print(f"\n{flmod.fluid}, composition: {self.composition}")
if pr.quality >= 0:
print(f"Quality: {pr.quality :.3f}")
for key, value in self.val_dict.items():
print(f"{key} : {value}")
[docs]
def calc_temp_mean(self, h_final):
"""
Calculate the thermodynamic mean temperature between the actual state
and the final enthalpy along an isobaric (Delta h /Delta s)
Parameters
----------
h_final : float
enthalpy of he final stat.
Returns
-------
temp_mean : float
the thermodynamic mean temperature.
"""
actual_props = self.properties.state
final_props = self.set_state([h_final, actual_props[1]], "HP")
temp_mean = (final_props[2] - actual_props[2]) \
/ (final_props[4] - actual_props[4])
return temp_mean
[docs]
def init_fluid(fluid, composition, **keywords):
"""
short way to define a Fluid and a FluidModel
Parameters
----------
fluid : string
The species within the fluid.
composition : List
mole fraction for each fluid.
**keywords : TYPE
all keywords needed for the FluidModel, if non-defaults shall be set.
Returns
-------
actual_fluid : Fluid
Instance of the actually set Fluid.
"""
flmod = FluidModel(fluid, composition, **keywords)
actual_fluid = Fluid(flmod, composition)
return actual_fluid
def _scalar(x):
x = np.asarray(x)
if x.size != 1:
raise ValueError(f"Expected scalar, got shape {x.shape}")
return float(x.reshape(()))
if __name__ == "__main__":
import time
[docs]
FLUID = "Propane * Pentane"
comp = [.50, 0.5]
P_ACT = 1e5
T_ACT = 370
flm = FluidModel(FLUID)
myFluid = Fluid(flm, comp)
st00 = myFluid.set_state([T_ACT, P_ACT], "TP")
st1 = myFluid.set_state([T_ACT, P_ACT], "TP",
_TRANS_STRING)
print(st00, st1)
myFluid.print_state()
myFluid.set_composition([.2, .8])
t0 = time.time()
for ii in range(500):
st0 = myFluid.set_state([T_ACT, P_ACT], "TP", composition=[.35, .65])
t1 = time.time()
myFluid.print_state()
mean_temp_act = myFluid.calc_temp_mean(st0[2] + 1e5)
print(f"Mean Temperature {mean_temp_act} K")
# for testing water:
water = init_fluid("H2O", [1.0])
T_WATER = 300.
water.set_state([T_WATER, P_ACT], "TP", _TRANS_STRING)
print(f"""\nWater at {T_WATER} K, {P_ACT / 1e5} bar.
Cp ={water.val_dict['isobaric_heat_capacity']} ,
Pr: {water.val_dict['Prandtl_number']}\n""")
# value_vec = np.array([[300, 1e5], [400, 1e5], [500, 1e5]])
# stv = myFluid.set_state_v(value_vec, "TP")
# print(myFluid.set_state_v(value_vec, "TP"))
# print(myFluid.set_state([300., 1.], "TQ"))
# New simple way to get an instance of Fluid
myFluid2 = init_fluid(FLUID, comp)
# Refprop with mass fractions as input
print('\n---------------- Refprop Mass fraction Input')
FLUID = "Propane * Pentane"
comp = [.5, .5]
myFluid = init_fluid(FLUID, comp)
myFluidb = init_fluid("H2O", [1])
QUALITY = 0.5 # is interpreted by carbatpy as mol/mol to calculate state
# but given back in string as kg/kg
ta0 = time.time()
for ii in range(7):
stold = st0
st0 = myFluid.set_state([300., QUALITY], "TQ") # , dll_select="dll")
sth2o = myFluidb.set_state([310, 1e5], "TP")
# print (st0-stold)
stold = st0
ta1 = time.time()
print(f"Durations all: {ta1 - ta0}, 2dll: {t1 - t0}")
myFluid.print_state()
st0n = myFluid.set_state([st0[1], st0[2]], "PH", dll_select="dll")
myFluid.print_state()
print(f"quality prescribed: {QUALITY}")
print(f"resulting quality from carbatpy in st0: {st0[5]}")
print("\n------------\n")
# Trend: --------------------------------------
my_dict = {#"Input": "TP",
#'calctype': "H",
#'fluids': FLUID,
#"moles": comp,
"eos_ind": [1, 1],
'mix_ind': 1,
#'path': trend_path,
'units': 'specific',
#'dll_path': trend_dll
}
comp =[.5,0.5]
my_tr_fl = init_fluid(FLUID, composition=comp, props="TREND")#, **my_dict)
# [0.6206645653468038, 0.3793354346531962])
state_trend = my_tr_fl.set_state([T_ACT, P_ACT], "TP")
print("\nComparison Refprop vs. Trend\n", st00, "\n", state_trend,
my_tr_fl.properties.enthalpy)
state_tr_trans = my_tr_fl.set_state([T_ACT, P_ACT], "TP", wanted=_TRANS_TREND_MIX)
print(f"Trans-String TREND: {state_tr_trans}")
# shortcut to generate a trend fluid
flt0 = carbatpy.init_fluid(FLUID, comp, props="TREND", **my_dict)
# saturated mixture:
state_trend_satp = my_tr_fl.set_state([P_ACT, 0], "PQ")
print("\nSaturated mixture, liq:\n", state_trend_satp, my_tr_fl.properties.enthalpy)
state_trend_satp = my_tr_fl.set_state([P_ACT, 1], "PQ")
print("Saturated mixture, vap:\n", state_trend_satp, my_tr_fl.properties.enthalpy)
# TREND two-phase
print("---------------------two-phase TREND------------------------------")
state_2p = my_tr_fl.set_state([267, 1e5], "TP")
my_tr_fl.print_state()
# some temperature dependence
state_trend_c = my_tr_fl.set_state([267., P_ACT], "TP")
enth = np.linspace(state_trend_c[2], state_trend[2], 25)
all_props_act = []
for h_act in enth:
all_props_act.append(my_tr_fl.set_state([P_ACT, h_act], "PH"))
all_props_act = np.array(all_props_act)
import matplotlib.pyplot as plt
fi, ax = plt.subplots(1)
ax.plot(all_props_act[:, 2], all_props_act[:, 0])
# return a FluidState
my_prop = my_tr_fl.set_state([T_ACT, P_ACT], "TP", output="FluidState")
my_prop2 = my_tr_fl.set_state([T_ACT + 25, P_ACT], "TP", output="FluidState")
print(f'Fluidstate as output: {my_prop.enthalpy, my_prop2.enthalpy}')
my_prop = myFluid.set_state([T_ACT, P_ACT], "TP", output="FluidState")
my_prop2 = myFluid.set_state([T_ACT + 25, P_ACT], "TP", output="FluidState")
print(f'Fluidstate as output: {my_prop.enthalpy, my_prop2.enthalpy}')
# test new multiREFPROPstructure
fluids = ["water", "Butane * Propane"]
instances = [False, True]
printing_handles = ["---0---",
"---1---"]
# one instance, new init called each time
for i, rp_inst in enumerate(instances):
print(f"\n {printing_handles[i]}")
water = init_fluid(fluids[0], [1], rp_inst=rp_inst)
mixture = init_fluid(fluids[1], [0.2, 0.8], rp_inst=rp_inst)
t0 = time.time()
for i in range(1):
water.set_state([350, 0.5], "TQMASS")
#print(water.properties.pressure)
#for i in range(1000):
mixture.set_state([310, 1e6], "TP")
#print(mixture.properties.pressure)
t1 = time.time()
print(f"thought for {t1 - t0} seconds")
print(water.properties.pressure)
print(mixture.properties.entropy)
print(f"Hi! I am Refprop Instance no: {water.fluidmodel.fl_instance.managed_handle}!")
print(f"Hi! I am Refprop Instance no: {mixture.fluidmodel.fl_instance.managed_handle}!")
# test set_composition
print("\n")
print("---test setup mixture composition---")
fluidname = "Butane * Propane"
comps = np.random.dirichlet(alpha=[1, 1], size=100)
#Variante 1: eine Instanz und set composition
t0 = time.time()
mixture = init_fluid(fluidname, [0.1,0.9])#comps[0,:])
for comp in comps:
mixture.set_composition(comp)
mixture.set_state([350, 0.5], "TQ")
t1 = time.time()
print(f"thought for {t1 - t0} seconds")
#Variante 2: immer neue Init mit einer Instanz
fluidname = "Butane * Propane"
instances = [0, 1]
# Variante 1: eine Instanz
t0 = time.time()
for comp in comps:
mixture = init_fluid(fluidname, comps[0])
mixture.set_state([350, 0.5], "TQ")
t1 = time.time()
print(f"thought for {t1 - t0} seconds")
# #Variante 3: Viele Instanzen, nicht empfohlen
# fluidname = "Butane * Propane"
# instances = [0, 1]
# # Variante 1: eine Instanz
# t0 = time.time()
# for comp in comps:
# mixture = init_fluid(fluidname, comps[0], rp_inst=1)
# mixture.set_state([350, 0.5], "TQ")
# t1 = time.time()
# print(f"thought for {t1 - t0} seconds")
# use TREND
fluid = "Propane * Butane"
comp = [0.2, 0.8]
# easy with default:
trend_easy = init_fluid(fluid, comp, props="TREND")
fluid_models = {
"DEF": {"model": "", "no": 1},
"Gerg": {"model": "\\gerg-2008", "no": 1},
"PR": {"model": "", "no": 3},
}
for key, items in fluid_models.items():
my_dict = {"eos_ind": [items["no"], items["no"]],
'mix_ind': items["no"],
'trendmodel': items['model'],
'props': "TREND"}
tr_fluid = init_fluid(fluid, comp, **my_dict)
tr_fluid.set_state([310, 0.6e5], "TP")
print(tr_fluid.properties.sp_volume)