Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add friction model "Hofer" #683

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
Change Log
=============

[upcoming release] - 2025-xx-xx
-------------------------------
- [ADDED] Hofer formula for friction factor "lambda"

[0.11.0] - 2024-11-07
-------------------------------
- [ADDED] heat_consumer plotting
Expand Down Expand Up @@ -28,8 +32,6 @@ Change Log
- [FIXED] alpha also applied to mdot
- [REMOVED] support for Python 3.8 due to EOL



[0.10.0] - 2024-04-09
-------------------------------

Expand Down
39 changes: 29 additions & 10 deletions doc/source/components/pipe/pipe_component.rst
Original file line number Diff line number Diff line change
Expand Up @@ -142,46 +142,65 @@ pipe sections.
Friction models
^^^^^^^^^^^^^^^

Three friction models are used to calculate the velocity dependent friction factor:
For friction models are available to calculate the velocity dependent friction factor :math:`\lambda`:
jkisse marked this conversation as resolved.
Show resolved Hide resolved

- Nikuradse
- Prandtl-Colebrook
- Swamee-Jain
- Nikuradse ("nikuradse")
- Prandtl-Colebrook ("colebrook")
- Hofer ("hofer")
- Swamee-Jain ("swamee-jain")

Nikuradse is chosen by default. In this case, the friction factor is calculated by:
They are set by the :code:`friction_model` parameter and the name given in parentheses.
*Nikuradse* is chosen by default. In this case, the friction factor is calculated by:

.. math::
:nowrap:

\begin{align*}
\lambda &= \frac{64}{Re} + \frac{1}{(-2 \cdot \log (\frac{k}{3.71 \cdot d}))^2}\\
\lambda &= \frac{64}{Re} + \frac{1}{\left(-2 \cdot \log \left(\frac{k}{3.71 \cdot d}\right)\right)^2}\\
\end{align*}


Note that in literature, Nikuradse is known as a model for turbulent flows. In pandapipes, the formula for the
Nikuradse model is also applied for laminar flow.

If Prandtl-Colebrook is selected, the friction factor is calculated iteratively according to
If *Prandtl-Colebrook* (also known as Colebrook-White) is selected, the friction factor is calculated iteratively according to

.. math::
:nowrap:

\begin{align*}
\frac{1}{\sqrt{\lambda}} &= -2 \cdot \log (\frac{2.51}{Re \cdot \sqrt{\lambda}} + \frac{k}{3.71 \cdot d})\\
\frac{1}{\sqrt{\lambda}} &= -2 \cdot \log \left(\frac{2.51}{Re \cdot \sqrt{\lambda}} + \frac{k}{3.71 \cdot d}\right)\\
\end{align*}

Equations for pressure losses due to friction were taken from :cite:`Eberhard1990` and
:cite:`Cerbe2008`.

The equation according to Swamee-Jain :cite:`Swamee1976` is an approximation of the calculation method according
The *Hofer-Equation* is an explicit approximation of the Prandtl-Colebrook method and defined as shown in
:cite:`Benner.2019` (based on :cite:`Hofer.1973`)

.. math::
:nowrap:

\begin{align*}
\lambda_H &= \frac{1}{(-2 \log \left(\frac{4.518}{Re} \cdot \log \left(\frac{Re}{7}) + \frac{k}{3.71 \cdot d}\right)\right)^2}\\
\end{align*}

Very small Reynolds numbers can lead to problems with the two nested log-functions.
Thus, in pandapipes, the laminar :math:`\lambda_l = 64/Re` is applied for small Reynolds numbers
(:math:`Re < \underline{Re}`) and :math:`\lambda_H` is used for high Reynolds numbers (:math:`Re > \overline{Re}`).
In the transition area :math:`\underline{Re} \leq Re \leq \overline{Re}`, :math:`\lambda_H` is interpolated linearly.
By default, the boundaries of this transition area are set to :math:`\underline{Re}=2000` and :math:`\overline{Re}=3000`.
Note that the derivative used for Hofer is the same as for Prandtl-Colebrook.
Copy link
Contributor

@kbensafta kbensafta Jan 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the PC-derivative indeed applicable in this case or is it an approximation, since Hofer is just an explicit form of Prandtl-Colebrook?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure - I found it acceptable, also because of the quite complicated derivative of the Hofer formula. Apparently it is working ; )
Maybe the convergence behaviour could be strengthened if a separate "Hofer-derivative" is implemented, but I find it sufficient so far.


The equation according to *Swamee-Jain* :cite:`Swamee1976` is another approximation of the calculation method according
to Prandtl-Colebrook. It is an explicit formula for the friction factor of the transition
zone of turbulent flows in pipes and is defined as follows:

.. math::
:nowrap:

\begin{align*}
\lambda &= \frac{0.25}{(\log(\frac{k}{3.7 \cdot d} + \frac{5.74}{Re^{0.9}}))^2}\\
\lambda &= \frac{0.25}{\left(\log\left(\frac{k}{3.7 \cdot d} + \frac{5.74}{Re^{0.9}}\right)\right)^2}\\
\end{align*}


Expand Down
4 changes: 2 additions & 2 deletions doc/source/pipeflow/calculation_modes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@ In gas flows, the velocity is typically not constant along a pipeline. For this
tables for pipes show more entries in comparison with the result tables for incompressible media.


Temperature calculations (pipeflow option: mode = "sequential", mode = "bidrectional" or mode = "heat")
=======================================================================================================
Temperature calculations (pipeflow option: mode = "sequential", mode = "bidirectional" or mode = "heat")
========================================================================================================

Important parameters of the network main components (junctions and pipes) needed for the calculation
are listed in the following table. The :ref:`component section <components>` of this manual contains
Expand Down
38 changes: 37 additions & 1 deletion doc/source/references.bib
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,40 @@ @article{Schmidt.2015
issn = {1389-4420},
journal = {Optimization and Engineering},
doi = {10.1007/s11081-014-9246-x}
}
}


@incollection{Benner.2019,
author = {Benner, Peter and Grundel, Sara and Himpe, Christian and Huck, Christoph and Streubel, Tom and Tischendorf, Caren},
title = {Gas Network Benchmark Models},
pages = {171--197},
publisher = {{Springer International Publishing}},
isbn = {978-3-030-03717-8},
series = {Differential-Algebraic Equations Forum},
editor = {Campbell, Stephen and Ilchmann, Achim and Mehrmann, Volker and Reis, Timo},
booktitle = {Applications of Differential-Algebraic Equations: Examples and Benchmarks},
year = {2019},
address = {Cham},
doi = {10.1007/11221_2018_5},
}

@book{Campbell.2019,
year = {2019},
title = {Applications of Differential-Algebraic Equations: Examples and Benchmarks},
address = {Cham},
publisher = {{Springer International Publishing}},
isbn = {978-3-030-03717-8},
series = {Differential-Algebraic Equations Forum},
editor = {Campbell, Stephen and Ilchmann, Achim and Mehrmann, Volker and Reis, Timo},
doi = {10.1007/978-3-030-03718-5}
}

@article{Hofer.1973,
author = {Hofer, P.},
year = {1973},
title = {{Beurteilung von Fehlern in Rohrnetzberechnungen}},
pages = {113--119},
volume = {114},
number = {3},
journal = {Das Gas- und Wasserfach. GWF - Ausgabe Wasser, Abwasser}
}
43 changes: 33 additions & 10 deletions src/pandapipes/pf/derivative_calculation.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,20 @@ def get_derived_values(node_pit, from_nodes, to_nodes, use_numba):
return calc_derived_values_np(node_pit, from_nodes, to_nodes)


def calc_lambda_transition_area(lambda_laminar, lambda_turb, re, begin_transition_re=2000,
end_transition_re=3000):
"""
Linear interpolation in the transition area between laminar and turbulent flow. Critical
point is usually at Reynolds = 2300, thus the default transition area is around that point.
"""
lambda_tot = lambda_laminar
share_turb = (re - begin_transition_re) / (end_transition_re - begin_transition_re)
lambda_transition = (1 - share_turb) * lambda_laminar + share_turb * lambda_turb
lambda_tot[(re >= begin_transition_re) & (re < end_transition_re)] = \
lambda_transition[(re >= begin_transition_re) & (re < end_transition_re)]
lambda_tot[re >= end_transition_re] = lambda_turb[re >= end_transition_re]
return lambda_tot

def calc_lambda(m, eta, d, k, gas_mode, friction_model, lengths, options, area):
"""
Function calculates the friction factor of a pipe. Turbulence is calculated based on
Expand Down Expand Up @@ -171,16 +185,19 @@ def calc_lambda(m, eta, d, k, gas_mode, friction_model, lengths, options, area):
from pandapipes.pf.derivative_toolbox_numba import calc_lambda_nikuradse_incomp_numba as \
calc_lambda_nikuradse_incomp, colebrook_numba as colebrook, \
calc_lambda_nikuradse_comp_numba as calc_lambda_nikuradse_comp
from pandapipes.pf.derivative_toolbox import calc_lambda_hofer_comp_np as calc_lambda_hofer_comp # numba version
# not yet implemented
else:
from pandapipes.pf.derivative_toolbox import calc_lambda_nikuradse_incomp_np as \
calc_lambda_nikuradse_incomp, colebrook_np as colebrook, \
calc_lambda_nikuradse_comp_np as calc_lambda_nikuradse_comp
calc_lambda_nikuradse_comp_np as calc_lambda_nikuradse_comp, \
calc_lambda_hofer_comp_np as calc_lambda_hofer_comp
if gas_mode:
re, lambda_laminar, lambda_nikuradse = calc_lambda_nikuradse_comp(m, d, k, eta, area)
else:
re, lambda_laminar, lambda_nikuradse = calc_lambda_nikuradse_incomp(m, d, k, eta, area)

if friction_model == "colebrook":
if friction_model.lower() == "colebrook":
# TODO: move this import to top level if possible
from pandapipes.pipeflow import PipeflowNotConverged
max_iter = options.get("max_iter_colebrook", 100)
Expand All @@ -192,9 +209,14 @@ def calc_lambda(m, eta, d, k, gas_mode, friction_model, lengths, options, area):
"inconsistencies. The maximum iterations can be given as 'max_iter_colebrook' "
"argument to the pipeflow.")
return lambda_colebrook, re
elif friction_model == "swamee-jain":
elif friction_model.lower() == "swamee-jain":
lambda_swamee_jain = 0.25 / ((np.log10(k / (3.7 * d) + 5.74 / (re ** 0.9))) ** 2)
return lambda_swamee_jain, re
elif friction_model.lower() == "hofer":
re, lambda_hofer = calc_lambda_hofer_comp(m, d, k, eta, area)
lambda_tot = calc_lambda_transition_area(lambda_laminar, lambda_hofer, re,
begin_transition_re=2000, end_transition_re=4000)
return lambda_tot, re
else:
# lambda_tot = np.where(re > 2300, lambda_laminar + lambda_nikuradse, lambda_laminar)
lambda_tot = lambda_laminar + lambda_nikuradse
Expand All @@ -203,12 +225,12 @@ def calc_lambda(m, eta, d, k, gas_mode, friction_model, lengths, options, area):

def calc_der_lambda(m, eta, d, k, friction_model, lambda_pipe, area):
"""
Function calculates the derivative of lambda with respect to v. Turbulence is calculated based
on Nikuradse. This should not be a problem as the pressure loss term will equal zero
(lambda * u^2).
Function calculates the derivative of lambda with respect to m (mass flow rate). Turbulence is
calculated based on Nikuradse. This should not be a problem as the pressure loss term will
equal zero (lambda * u^2).

:param v:
:type v:
:param m:
:type m:
:param eta:
:type eta:
:param rho:
Expand All @@ -231,7 +253,8 @@ def calc_der_lambda(m, eta, d, k, friction_model, lambda_pipe, area):
lambda_der = np.zeros_like(m)
pos = m != 0

if friction_model == "colebrook":
if friction_model.lower() in ["colebrook", "hofer"]: # hofer is explicit approximation of
# colebrook
b_term[pos] = (2.51 * eta[pos] * area[pos] / (m[pos] * d[pos] * np.sqrt(lambda_pipe[pos])) +
k[pos] / (3.71 * d[pos]))

Expand All @@ -244,7 +267,7 @@ def calc_der_lambda(m, eta, d, k, friction_model, lambda_pipe, area):
lambda_der[pos] = df_dm[pos] / df_dlambda[pos]

return lambda_der
elif friction_model == "swamee-jain":
elif friction_model.lower() == "swamee-jain":
param = (k[pos] / (3.7 * d[pos]) + 5.74 * ((eta[pos] * area[pos]) /
(np.abs(m[pos]) * d[pos])) ** 0.9)
# 0.5 / (log(10) * log(param)^3 * param) * 5.166 * abs(eta)^0.9 / (abs(rho * d)^0.9
Expand Down
32 changes: 27 additions & 5 deletions src/pandapipes/pf/derivative_toolbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import numpy as np
from numpy import linalg
from copy import deepcopy

from pandapipes.constants import P_CONVERSION, GRAVITATION_CONSTANT, NORMAL_PRESSURE, \
NORMAL_TEMPERATURE
Expand Down Expand Up @@ -73,24 +74,45 @@ def derivatives_hydraulic_comp_np(node_pit, branch_pit, lambda_, der_lambda, p_i
return load_vec, load_vec_nodes_from, load_vec_nodes_to, df_dm, df_dm_nodes, df_dp, df_dp1


def calc_lambda_nikuradse_incomp_np(m, d, k, eta, area):
def calc_lambda_laminar(m, d, eta, area):
m_abs = np.abs(m)
re = np.divide(m_abs * d, eta * area)
lambda_laminar = np.zeros_like(m)
lambda_laminar[~np.isclose(re, 0)] = 64 / re[~np.isclose(re, 0)]
return lambda_laminar, re


def calc_lambda_nikuradse_incomp_np(m, d, k, eta, area):
lambda_laminar, re = calc_lambda_laminar(m, d, eta, area)
lambda_nikuradse = np.divide(1, (-2 * np.log10(k / (3.71 * d))) ** 2)
return re, lambda_laminar, lambda_nikuradse


def calc_lambda_nikuradse_comp_np(m, d, k, eta, area):
m_abs = np.abs(m)
re = np.divide(m_abs * d, eta * area)
lambda_laminar = np.zeros_like(m)
lambda_laminar[~np.isclose(re, 0)] = 64 / re[~np.isclose(re, 0)]
lambda_laminar, re = calc_lambda_laminar(m, d, eta, area)
lambda_nikuradse = np.divide(1, (2 * np.log10(d / k) + 1.14) ** 2)
return re, lambda_laminar, lambda_nikuradse


def calc_lambda_hofer_comp_np(m, d, k, eta, area, hofer_re_threshold=2000):
"""Calculate Lambda acc. to Hofer-Equation (explicit version of Colebrook) [1].
Due to nested log10 operations, small Reynolds numbers must be avoided.
It is justified because the laminar Lambda (instead of Hofer) will be applied for small
Reynolds numbers anyway.

[1]: P. Hofer. Beurteilung von Fehlern in Rohrnetzberechnungen (Error evaluation in calculation
of pipelines). GWF–Gas/Erdgas, 114(3):113–119, 1973
"""
lambda_laminar, re = calc_lambda_laminar(m, d, eta, area)
lambda_hofer = np.zeros_like(re)
re_lower_lim = deepcopy(re)
re_lower_lim[re < hofer_re_threshold] = hofer_re_threshold
lambda_hofer = \
np.divide(1, (-2 * np.log10((4.518/re_lower_lim) * np.log10(re_lower_lim/7) + (k / (3.71 * d)))) ** 2)
lambda_hofer[re < hofer_re_threshold] = 0
return re, lambda_hofer


def calc_medium_pressure_with_derivative_np(p_init_i_abs, p_init_i1_abs):
val = 2 / 3
p_m = p_init_i_abs.copy()
Expand Down
33 changes: 29 additions & 4 deletions src/pandapipes/pf/derivative_toolbox_numba.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,15 +111,40 @@ def calc_lambda_nikuradse_comp_numba(m, d, k, eta, area):
lambda_nikuradse = np.empty_like(m)
lambda_laminar = np.zeros_like(m)
re = np.empty_like(m)
m_abs = np.abs(m)
for i, mi in enumerate(m):
m_abs = np.abs(mi)
re[i] = np.divide(m_abs * d[i], eta[i] * area[i])
re[i] = (m_abs[i] * d[i]) / (eta[i] * area[i])
if re[i] != 0:
lambda_laminar[i] = np.divide(64, re[i])
lambda_nikuradse[i] = np.divide(1, (2 * np.log10(np.divide(d[i], k[i])) + 1.14) ** 2)
lambda_laminar[i] = 64 / re[i]
lambda_nikuradse[i] = (2 * np.log10((d[i] / k[i])) + 1.14) ** -2
return re, lambda_laminar, lambda_nikuradse


@jit((float64[:], float64[:], float64[:], float64[:], float64[:], int64), nopython=True)
def calc_lambda_hofer_comp_numba(m, d, k, eta, area, hofer_re_threshold=2000):
"""Calculate Lambda acc. to Hofer-Equation (explicit version of Colebrook) [1].
Due to nested log10 operations, small Reynolds numbers must be avoided.
It is justified because the laminar Lambda (instead of Hofer) will be applied for small
Reynolds numbers anyway.

[1]: P. Hofer. Beurteilung von Fehlern in Rohrnetzberechnungen (Error evaluation in calculation
of pipelines). GWF–Gas/Erdgas, 114(3):113–119, 1973
"""
lambda_hofer = np.zeros_like(m)
lambda_laminar = np.zeros_like(m)
re = np.empty_like(m)
m_abs = np.abs(m)
for i, mi in enumerate(m):
re[i] = (m_abs[i] * d[i]) / (eta[i] * area[i])
if re[i] != 0:
lambda_laminar[i] = 64 / re[i]
if re[i] >= hofer_re_threshold:
lambda_hofer[i] = (-2 * np.log10((4.518/re[i]) * np.log10(re[i]/7)
+ (k[i] / (3.71 * d[i])))
) ** -2
return re, lambda_laminar, lambda_hofer


@jit((float64[:], float64[:]), nopython=True, cache=False)
def calc_medium_pressure_with_derivative_numba(p_init_i_abs, p_init_i1_abs):
p_m = p_init_i_abs.copy()
Expand Down
Loading