Source code for carbatpy.models.cb_fluids.fluid_props

# -*- 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] VERBOSE = False
[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] units = 21
[docs] props = DEFAULTS["Fluid_Defaults"]['PROPS']
[docs] rp_inst = False
# TREND
[docs] eos_ind: Optional[List[int]] = None
[docs] eos_ind_default: int = 1
[docs] mix_ind = 1
[docs] trendmodel = ""
[docs] path = carbatpy.CB_DEFAULTS['Fluid_Defaults']['TREND']["TREND_PATH"]
[docs] dll_path = carbatpy.CB_DEFAULTS['Fluid_Defaults']['TREND']["TREND_DLL"]
# CUSTOM
[docs] props_func = None
[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)
[docs] self.fluid = fluid
[docs] self.props = cfg.props
[docs] self.units = cfg.units
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.properties = None
[docs] self.state_v = None
[docs] self.option = option
[docs] self.no_compounds = len(composition)
[docs] self.comp_mass = np.zeros(self.no_compounds)
[docs] self.herr = 0
[docs] self.props = fluidmodel.props
# BA something is wrong here (probably 2024-07-10 self.set_composition(composition)
[docs] self.val_dict = {}
[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)