How to use carbatpy to manage your fluid calls
installation
First things first: You need to have the fluid software properly set up and installed. At the moment, carbatpy offers
- REFPROP10For REFPROP, os.environ[‘RPPREFIX’] should be set. REFPROP offers a separate library for multithreading (REFPROPLibraryManager), this is explained further below. For this os.environment[‘REFPROPLibraryManager’] must be set. (see:https://github.com/usnistgov/REFPROP-manager/tree/master)
- TREND6For TREND, make sure the dll-path and the installation path is saved here: os.environ[‘TREND_DLL’] os.environ[‘TREND_PATH’].
- (Coolprop)The coolprop calls are not to date anymore and might need some work before being functional again.
[1]:
import carbatpy as cb
import numpy as np
import time
using REFPROP (without REFPROPLibraryManager)
The general logic is as follows: Define a string with the fluid (mixture) name - use * to provide several components in a mixture. Set an array containing the composition and call init_fluid(fluid_name, fluid_comp) to initialize it.
[2]:
fluid_name = "water"
fluid_comp = [1]
fluid_rp = cb.init_fluid(fluid_name, fluid_comp)
To define a state, call set_state([input1, input2], 'prop1prop2').
[3]:
fluid_rp.set_state([300, 1e5], "TP")
[3]:
array([ 3.00000000e+02, 1.00000000e+05, 1.12653680e+05, 1.00345556e-03,
3.93062434e+02, -9.99999000e+06, 1.12553334e+05])
Results can be accessed via:
[4]:
fluid_rp.val_dict
[4]:
{'Temperature': 300.0,
'Pressure': 99999.9999978389,
'spec_Enthalpy': 112653.67968890067,
'spec_Volume': 0.001003455559381887,
'spec_Entropy': 393.0624338156361,
'quality_mass': -9999990.0,
'spec_internal_Energy': 112553.33413296464,
'x': None,
'y': None,
'z': array('d', [1.0])}
Or you can access single properties by:
[5]:
fluid_rp.properties.enthalpy
[5]:
112653.67968890067
You can also save the new state for later use via:
[6]:
sat_state_0 = fluid_rp.set_state([300, 0], "TQ", output="FluidState")
sat_state_1 = fluid_rp.set_state([300, 1], "TQ", output="FluidState")
# calc evaporation enthalpy
sat_state_1.enthalpy - sat_state_0.enthalpy
[6]:
2437289.2412411724
carbatpy per default gets a bunch of properties per call to manage calculation time of the fluid property libraries. For convenience, they are sorted into thermodynamic properties and transport properties. Individual calls can be given into the command via the optional wanted argument.
[7]:
# default: use thermo_string
print(cb.CB_DEFAULTS["Fluid_Defaults"]['THERMO_STRING'])
# trans-string
trans = cb.CB_DEFAULTS["Fluid_Defaults"]["TRANS_STRING"]
print(trans)
fluid_rp.set_state([400, 1e5], "TP", wanted=trans)
print(fluid_rp.val_dict)
# if you need some other property, e.g. surface tension
fluid_rp.set_state([400, 1e5], "TP", wanted=trans+';STN')[-1]
T;P;H;V;S;QMASS;E
T;P;H;V;S;QMASS;E;VIS;TCX;PRANDTL;CP;W;M
{'Temperature': 400.0, 'Pressure': 100000.00000000004, 'spec_Enthalpy': 2730427.1568082636, 'spec_Volume': 1.826132416143826, 'spec_Entropy': 7502.525471878715, 'quality_mass': -9999990.0, 'spec_internal_Energy': 2547813.915193881, 'viscosity': 1.3277592251441171e-05, 'thermal_conductivity': 0.026824977826205194, 'Prandtl_number': 0.9937824261269568, 'isobaric_heat_capacity': 2007.7579609386255, 'speed_of_sound': 490.30699755473495, 'molecular_mass': 0.018015268, 'x': None, 'y': None, 'z': array('d', [1.0])}
[7]:
np.float64(0.05357792640201926)
using REFPROP (with REFPROPLibraryManager)
To use REFPROP in parallel, three ways are possible, as described here: https://deepwiki.com/usnistgov/REFPROP-wrappers/2.2-parallel-processing-with-ctrefprop In carbatpy, REFPROP can be utilized in parallel processes, but not in multiple threads. Using several fluids in one script, however, is not a problem as they are called subsequently! Thus, using the REFPROPLibraryManager is optional, but can be much faster (pointed out further below).
The REFPROPLibraryManager needs to be downloaded and build via https://github.com/usnistgov/REFPROP-manager The path to the built dll (REFMAN.dll) needs to be set as os/user variable named: REFPROPLibraryManager.
Afterwards, usage via carbatpy is fairly easy: init_fluid has the optional parameter rp_inst. Per default, it is set to 0 and no REFPROPLibraryManager is used. If its set to True, init_fluid creates a new, clean instance every time it is called. These instances are then threadsafe.
[8]:
water = cb.init_fluid("water", [1], rp_inst=True)
mixture = cb.init_fluid("Propane * Butane", [0.8, 0.2], rp_inst=True)
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}!")
Hi! I am Refprop Instance no: 1!
Hi! I am Refprop Instance no: 2!
[9]:
fluidname = "Butane * Propane"
comps = np.random.dirichlet(alpha=[1, 1], size=10)
print("option 1: one instance and set composition (favorite!)")
t0 = time.time()
mixture = cb.init_fluid(fluidname, [0.1, 0.9])
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\n")
print("option 2: one instance always calling init_fluid :-(")
t0 = time.time()
for comp in comps:
mixture = cb.init_fluid(fluidname, comp)
mixture.set_state([350, 0.5], "TQ")
t1 = time.time()
print(f"thought for {t1 - t0} seconds\n")
print("option 3: one NEW instance and set composition (like favorite!)")
t0 = time.time()
mixture = cb.init_fluid(fluidname, [0.1, 0.9], rp_inst=True)
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\n")
print("option 4: each time new instance, worst case szenario")
t0 = time.time()
for comp in comps:
mixture = cb.init_fluid(fluidname, comp, rp_inst=True)
mixture.set_state([350, 0.5], "TQ")
t1 = time.time()
print(f"thought for {t1 - t0} seconds\n")
option 1: one instance and set composition (favorite!)
thought for 0.07317399978637695 seconds
option 2: one instance always calling init_fluid :-(
thought for 0.3334512710571289 seconds
option 3: one NEW instance and set composition (like favorite!)
thought for 0.0795297622680664 seconds
option 4: each time new instance, worst case szenario
thought for 0.8618853092193604 seconds
Example 2: Should I use REFPROPLibraryManager
If multithreading is necessary for any reason, you HAVE TO use REFPROPLibraryManager. Otherwise, it might provide advantages in calculation time.
Scenario 1: Switching regularly, REFPROPLibraryManager MUCH faster.
Scenario 2: Calling different fluids subsequently, it actually makes no difference. The information is cached.
[10]:
# Scenario 1: Switching regularly
fluids = ["water", "Butane * Propane"]
instances = [0,1]
printing_handles = ["---0---",
"---1---"]
for i, rp_inst in enumerate(instances):
print(f"\n {printing_handles[i]}")
water = cb.init_fluid(fluids[0], [1], rp_inst=rp_inst)
mixture = cb.init_fluid(fluids[1], [0.2, 0.8], rp_inst=rp_inst)
t0 = time.time()
for i in range(1000):
water.set_state([350, 0.5], "TQ")
mixture.set_state([350, 0.5], "TQ")
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}!")
# Scenario 2: Calling subsequently
for i, rp_inst in enumerate(instances):
print(f"\n {printing_handles[i]}")
water = cb.init_fluid(fluids[0], [1], rp_inst=rp_inst)
mixture = cb.init_fluid(fluids[1], [0.2, 0.8], rp_inst=rp_inst)
t0 = time.time()
for i in range(1000):
water.set_state([350, 0.5], "TQ")
for i in range(1000):
mixture.set_state([350, 0.5], "TQ")
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}!")
---0---
thought for 16.227094173431396 seconds
41681.72973845631
2104.3140720367205
Hi! I am Refprop Instance no: None!
Hi! I am Refprop Instance no: None!
---1---
thought for 0.35153794288635254 seconds
41681.72973845631
2104.3140720367223
Hi! I am Refprop Instance no: 14!
Hi! I am Refprop Instance no: 15!
---0---
thought for 0.47821521759033203 seconds
41681.72973845631
2091.2595121512004
Hi! I am Refprop Instance no: None!
Hi! I am Refprop Instance no: None!
---1---
thought for 0.36737751960754395 seconds
41681.72973845631
2104.3140720367223
Hi! I am Refprop Instance no: 16!
Hi! I am Refprop Instance no: 17!
Using TREND6
Trend (Span et al., 2025), developed at RUB, offers several models to evaluate fluid data. For detailed information, please refer to the official documentation. If you simply want to use the default settings, add props='TREND' when calling init_fluid. This will use Helmholtz-EOS for all pure fluids and GERG-2004 mixing rules, if applicable.
[11]:
fluid = "Pentane * CO2"
comp = [0.95, 0.05]
tr_fluid = cb.init_fluid(fluid, comp, props='TREND')
tr_fluid.set_state([350, 1], "TQ")
tr_fluid.print_state()
['Pentane', 'CO2'], composition: [0.95, 0.05]
Quality: 1.000
Temperature : 350.0
Pressure : 359306.4266780964
spec_Enthalpy : 426220.60638465255
spec_Volume : 0.10309194671922216
spec_Entropy : 1284.3425453195803
quality_mass : 1.0
spec_internal_Energy : 389179.0073896798
x : None
y : None
z : None
To switch the used model, add additional keyword arguments either as **kwargs, or as optional arguments, or as a mix of both. Valid keywords are: mix_ind, eos_ind, trendmodel, path and dll_path for TREND. trendmodel may include subpaths like \\gerg-2008' or other submodels you have available. Examples: | model | trendmodel | eos_ind | mix_ind | | ———– | ———– | ———– | ———– | | Helmholtz-EOS | '' | [1,...]
| 1 | | Peng-Robinson | '' | [3,...] | 3 | | Gerg-2008 | \\gerg-2008 | [1,...] | 1 |
[12]:
tr_fluid_pr = cb.init_fluid(fluid, comp, props='TREND', eos_ind=[3,3], mix_ind=3)
tr_fluid_pr.set_state([350, 1], "TQ")
tr_fluid_pr.print_state()
#or if dictionary is preferred
trend_dict = {'eos_ind': [3, 3],
'mix_ind': 3,
}
tr_fluid_pr2 = cb.init_fluid(fluid, comp, props='TREND', **trend_dict)
tr_fluid_pr2.set_state([350, 1], "TQ")
tr_fluid_pr2.print_state()
tr_fluid_gerg = cb.init_fluid(fluid, comp, props='TREND', trendmodel="\\gerg-2008")
tr_fluid_gerg.set_state([350, 1], "TQ")
tr_fluid_gerg.print_state()
['Pentane', 'CO2'], composition: [0.95, 0.05]
Quality: 1.000
Temperature : 350.0
Pressure : 357956.1461085621
spec_Enthalpy : 79749.97393835131
spec_Volume : 0.10392295747414884
spec_Entropy : 134.56152686946132
quality_mass : 1.0
spec_internal_Energy : 42550.112588700984
x : None
y : None
z : None
['Pentane', 'CO2'], composition: [0.95, 0.05]
Quality: 1.000
Temperature : 350.0
Pressure : 357956.1461085621
spec_Enthalpy : 79749.97393835131
spec_Volume : 0.10392295747414884
spec_Entropy : 134.56152686946132
quality_mass : 1.0
spec_internal_Energy : 42550.112588700984
x : None
y : None
z : None
['Pentane', 'CO2'], composition: [0.95, 0.05]
Quality: 1.000
Temperature : 350.0
Pressure : 359144.09204508376
spec_Enthalpy : 409551.08337346255
spec_Volume : 0.10315214613789389
spec_Entropy : 1694.6760978130403
quality_mass : 1.0
spec_internal_Energy : 372504.5995062666
x : None
y : None
z : None
Hint: Previously, the TREND call was designed by a dict: my_dict = {"Input": "TP", 'calctype': "H", 'fluids': FLUID, "moles": comp, "eos_ind": [1, 1], 'mix_ind': 1, 'path': trend_path, 'unit': 'specific', 'dll_path': trend_dll} The arguments input and calctype have been removed, as they are obsolete and get replaced anyway when calling set_state. The arguments moles and fluids
have been removed, as they duplicated the non-optional function arguments and were prone to produce errors. All other arguments are still valid entries and can be adjusted as necessary.
Reference
Span, R.; Beckmüller, R.; Buchenfeld, J.; Fiedler, F.; Jäger, A.; Mickoleit, E.; Neumann, T.; Nguyen, T.-T.-G.; Pohl, S. M.; Rath, S.; Sabozin, F.; Semrau, B.; Thol, M. (2025): TREND. Thermodynamic Reference and Engineering Data 6.0. Lehrstuhl für Thermodynamik, Ruhr-Universität Bochum.
[ ]: