# -*- coding: utf-8
"""Module for fluid property helper functions.
This file is part of project TESPy (github.com/oemof/tespy). It's copyrighted
by the contributors recorded in the version control history of the file,
available from its original location
tespy/tools/fluid_properties/helpers.py
SPDX-License-Identifier: MIT
"""
import math
import CoolProp.CoolProp as CP
import numpy as np
from tespy.tools.global_vars import ERR
from tespy.tools.helpers import central_difference
from tespy.tools.helpers import newton_with_kwargs
def _is_larger_than_precision(value):
return value > ERR
[docs]
def get_number_of_fluids(fluid_data):
return sum([1 for f in fluid_data.values() if _is_larger_than_precision(f["mass_fraction"])])
[docs]
def get_pure_fluid(fluid_data):
for f in fluid_data.values():
if _is_larger_than_precision(f["mass_fraction"]):
return f
[docs]
def single_fluid(fluid_data):
r"""Return the name of the pure fluid in a fluid vector."""
if "_HUMID_AIR" in fluid_data:
return None
if get_number_of_fluids(fluid_data) > 1:
return None
else:
for fluid, data in fluid_data.items():
if _is_larger_than_precision(data["mass_fraction"]):
return fluid
[docs]
def get_molar_fractions(fluid_data):
molarflow = {
key: value["mass_fraction"] / value["wrapper"]._molar_mass
for key, value in fluid_data.items()
}
molarflow_sum = sum(molarflow.values())
return {key: value / molarflow_sum for key, value in molarflow.items()}
[docs]
def inverse_temperature_mixture(p=None, target_value=None, fluid_data=None, T0=None, f=None):
# calculate the fluid properties for fluid mixtures
valmin, valmax = get_mixture_temperature_range(fluid_data)
if T0 is None or T0 == 0 or np.isnan(T0) or T0 < valmin:
T0 = (valmin + valmax) / 2.0
# this is to prevent evaluation below valmin!
valmin *= 1.001
valmax *= 2
function_kwargs = {
"p": p, "fluid_data": fluid_data, "T": T0,
"function": f, "parameter": "T" , "delta": 0.001
}
return newton_with_kwargs(
central_difference,
target_value,
val0=T0,
valmin=valmin,
valmax=valmax,
**function_kwargs
)
[docs]
def get_mixture_temperature_range(fluid_data):
valmin = max(
[v["wrapper"]._T_min for v in fluid_data.values() if _is_larger_than_precision(v["mass_fraction"])]
) + 0.1
valmax = min(
[v["wrapper"]._T_max for v in fluid_data.values() if _is_larger_than_precision(v["mass_fraction"])]
) - 0.1
return valmin, valmax
[docs]
def calc_molar_mass_mixture(fluid_data, molar_fractions):
return sum([x * fluid_data[fluid]["wrapper"]._molar_mass for fluid, x in molar_fractions.items()])
[docs]
def fluid_structure(fluid):
r"""
Return the checmical formula of fluid.
Parameters
----------
fluid : str
Name of the fluid.
Returns
-------
parts : dict
Dictionary of the chemical base elements as keys and the number of
atoms in a molecule as values.
Example
-------
Get the chemical formula of methane.
>>> from tespy.tools.fluid_properties.helpers import fluid_structure
>>> elements = fluid_structure('methane')
>>> elements['C'], elements['H']
(1, 4)
"""
parts = {}
for element in CP.get_fluid_param_string(
fluid, 'formula').split('}'):
if element != '':
el = element.split('_{')
parts[el[0]] = int(el[1])
return parts
[docs]
def darcy_friction_factor(re, ks, d):
r"""
Calculate the Darcy friction factor.
Parameters
----------
re : float
Reynolds number re / 1.
ks : float
Pipe roughness ks / m.
d : float
Pipe diameter/characteristic length d / m.
Returns
-------
darcy_friction_factor : float
Darcy friction factor :math:`\lambda` / 1
Note
----
**Laminar flow** (:math:`re \leq 2320`)
.. math::
\lambda = \frac{64}{re}
**turbulent flow** (:math:`re > 2320`)
*hydraulically smooth:* :math:`\frac{re \cdot k_{s}}{d} < 65`
.. math::
\lambda = \begin{cases}
0.03164 \cdot re^{-0.25} & re \leq 10^4\\
\left(1.8 \cdot \log \left(re\right) -1.5 \right)^{-2} &
10^4 < re < 10^6\\
solve \left(0 = 2 \cdot \log\left(re \cdot \sqrt{\lambda} \right) -0.8
- \frac{1}{\sqrt{\lambda}}\right) & re \geq 10^6\\
\end{cases}
*transition zone and hydraulically rough:*
.. math::
\lambda = solve \left( 0 = 2 \cdot \log \left( \frac{2.51}{re \cdot
\sqrt{\lambda}} + \frac{k_{s}}{d \cdot 3.71} \right) -
\frac{1}{\sqrt{\lambda}} \right)
Reference: :cite:`Nirschl2018`.
Example
-------
Calculate the Darcy friction factor at different hydraulic states.
>>> from tespy.tools.fluid_properties.helpers import darcy_friction_factor
>>> ks = 5e-5
>>> d = 0.05
>>> re_laminar = 2000
>>> re_turb_smooth = 5000
>>> re_turb_trans = 70000
>>> re_high = 1000000
>>> d_high = 0.8
>>> re_very_high = 6000000
>>> d_very_high = 1
>>> ks_low = 1e-5
>>> ks_rough = 1e-3
>>> darcy_friction_factor(re_laminar, ks, d)
0.032
>>> round(darcy_friction_factor(re_turb_smooth, ks, d), 3)
0.038
>>> round(darcy_friction_factor(re_turb_trans, ks, d), 3)
0.023
>>> round(darcy_friction_factor(re_turb_trans, ks_rough, d), 3)
0.049
>>> round(darcy_friction_factor(re_high, ks, d_high), 3)
0.012
>>> round(darcy_friction_factor(re_very_high, ks_low, d_very_high), 3)
0.009
"""
if re <= 2320:
return 64 / re
else:
if re * ks / d < 65:
if re <= 1e4:
return blasius(re)
elif re < 1e6:
return hanakov(re)
else:
l0 = 0.02
function_kwargs = {
"function": prandtl_karman,
"parameter": "darcy_friction_factor",
"reynolds": re
}
return newton_with_kwargs(
prandtl_karman_derivative,
0,
val0=l0,
valmin=0.00001,
valmax=0.2,
**function_kwargs
)
else:
l0 = 0.002
function_kwargs = {
"function": colebrook,
"parameter": "darcy_friction_factor",
"reynolds": re,
"ks": ks,
"diameter": d,
"delta": 0.001
}
return newton_with_kwargs(
central_difference,
0,
val0=l0,
valmin=0.0001,
valmax=0.2,
**function_kwargs
)
[docs]
def blasius(re):
"""
Calculate friction coefficient according to Blasius.
Parameters
----------
re : float
Reynolds number.
Returns
-------
darcy_friction_factor : float
Darcy friction factor.
"""
return 0.3164 * re ** (-0.25)
[docs]
def hanakov(re):
"""
Calculate friction coefficient according to Hanakov.
Parameters
----------
re : float
Reynolds number.
Returns
-------
darcy_friction_factor : float
Darcy friction factor.
"""
return (1.8 * math.log10(re) - 1.5) ** (-2)
[docs]
def prandtl_karman(reynolds, darcy_friction_factor, **kwargs):
"""
Calculate friction coefficient according to Prandtl and v. Kármán.
Applied in smooth conditions.
Parameters
----------
re : float
Reynolds number.
darcy_friction_factor : float
Darcy friction factor.
Returns
-------
darcy_friction_factor : float
Darcy friction factor.
"""
return (
2 * math.log10(reynolds * darcy_friction_factor ** 0.5)
- 0.8 - 1 / darcy_friction_factor ** 0.5
)
[docs]
def prandtl_karman_derivative(reynolds, darcy_friction_factor, **kwargs):
"""Calculate derivative for Prandtl and v. Kármán equation."""
return (
1 / (darcy_friction_factor * math.log(10))
+ 0.5 * darcy_friction_factor ** (-1.5)
)
[docs]
def colebrook(reynolds, ks, diameter, darcy_friction_factor, **kwargs):
"""
Calculate friction coefficient according to Colebrook-White equation.
Applied in transition zone and rough conditions.
Parameters
----------
re : float
Reynolds number.
ks : float
Equivalent sand roughness.
d : float
Pipe's diameter.
darcy_friction_factor : float
Darcy friction factor.
Returns
-------
darcy_friction_factor : float
Darcy friction factor.
"""
return (
2 * math.log10(
2.51 / (reynolds * darcy_friction_factor ** 0.5) + ks
/ (3.71 * diameter)
) + 1 / darcy_friction_factor ** 0.5
)
def _check_fitting_data_structure(x: np.ndarray, y: np.ndarray) -> None:
if len(x) != len(y):
msg = ""
raise ValueError(msg)
elif len(x) < 2:
msg = ""
raise ValueError(msg)
[docs]
def fit_incompressible_viscosity(temperature: np.ndarray, viscosity: np.ndarray) -> tuple:
_check_fitting_data_structure(temperature, viscosity)
x = 1.0 / temperature
y = np.log(viscosity)
return np.polyfit(x, y, 3)
def _fit_arrhenius(temperature: np.ndarray, viscosity: np.ndarray) -> tuple:
_check_fitting_data_structure(temperature, viscosity)
x = 1.0 / temperature
y = np.log(viscosity)
intercept, slope = np.polyfit(x, y, 1)
return intercept, np.exp(slope)
[docs]
def fit_incompressible_linear(temperature: np.ndarray, y: np.ndarray) -> tuple:
_check_fitting_data_structure(temperature, y)
return np.polyfit(temperature, y, 1)