Turbulent Pipe Flow (Reynolds 180)

Turbulent Pipe Flow (Reynolds 180)#

The simulation of a periodic turbulent pipe flow is used for the validation of the immersed boundary method effectiveness to delineate a curved boundary in a turbulent flow. As for the turbulent channel case, the external force density term represents the pressure gradient \(F_{x}=-\mathrm{d}p/\mathrm{d}x\), the regularized no-slip BC is employed at \(y=0\) and \(y=H\), and periodicity is considered in the remaining boundaries.

Note: A precursor turbulent flow is used to start the simulation and assure a turbulent flow. This field can be generated through a precursor poiseuille flow simulation with an Lagrangian body placed to generate perturbations and removed after turbulence is generated.

from nassu.cfg.model import ConfigScheme

filename = "tests/validation/cases/05_turbulent_pipe_flow.nassu.yaml"

sim_cfgs = ConfigScheme.sim_cfgs_from_file_dct(filename)

An extra spacing of 2 lattices at each side of the cylinder is kept to assure a complete interpolation-spread procedure.

sim_cfg = next(iter(sim_cfgs.values()))

Functions used for processing of turbulent pipe

from nassu.cfg.schemes.simul import SimulationConfigs
import numpy as np

from vtk.util.numpy_support import vtk_to_numpy
from tests.validation.notebooks import common

stats_export = sim_cfg.output.stats["default"]
last_step = stats_export.interval.get_all_process_steps(sim_cfg.n_steps)[-1]
reader = stats_export.read_vtm_export(last_step)
reader_output = reader.GetOutput()


def get_macr_compressed(
    sim_cfg: SimulationConfigs, macr_name: str, is_2nd_order: bool
) -> tuple[np.ndarray, np.ndarray]:
    global reader, reader_output

    macr_name_read = macr_name if not is_2nd_order else f"{macr_name}_2nd"
    ds = sim_cfg.domain.domain_size

    p0 = np.array((ds.x // 2, 0, ds.z // 2))
    p1 = np.array((ds.x // 2, ds.y - 1, ds.z // 2))
    n_points = ds.y
    pos = np.linspace(p0, p1, num=n_points, endpoint=True)
    norm_pos = (pos[:, 1] + 0.5) / (len(pos) + 1)

    # Sum 0.5 because data is cell centered in vtm
    line = common.create_line(p0 + 0.5, p1 + 0.5, n_points - 1)

    probe_filter = common.probe_over_line(line, reader_output)
    probed_data = vtk_to_numpy(probe_filter.GetOutput().GetPointData().GetArray(macr_name_read))

    return norm_pos, probed_data

Results#

Load values for comparison

import pandas as pd
import numpy as np
import os

comparison_folder = "tests/validation/comparison/Turbulent_pipe/Re_tau=180"
files = ["ux_avg", "uang_rms", "ur_rms", "ux_rms"]
get_filename_csv = lambda f: os.path.join(comparison_folder, f + ".csv")


df_cp = {f: pd.read_csv(get_filename_csv(f), delimiter=",") for f in files}

Friction velocity and y+ to use

yp = 2.5
u_fric = 0.003401361 * 0.925

Load simulation velocity fields

pos, ux_avg = get_macr_compressed(sim_cfg, "ux", is_2nd_order=False)
pos, ux_2nd = get_macr_compressed(sim_cfg, "ux", is_2nd_order=True)
ux_rms = (ux_2nd - ux_avg**2) ** 0.5

pos, uy_avg = get_macr_compressed(sim_cfg, "uy", is_2nd_order=False)
pos, uy_2nd = get_macr_compressed(sim_cfg, "uy", is_2nd_order=True)
uy_rms = (uy_2nd - uy_avg**2) ** 0.5

pos, uz_avg = get_macr_compressed(sim_cfg, "uz", is_2nd_order=False)
pos, uz_2nd = get_macr_compressed(sim_cfg, "uz", is_2nd_order=True)
uz_rms = (uz_2nd - uz_avg**2) ** 0.5

ux_avg.shape, ux_rms.shape
((152,), (152,))
wall_pos = 5  # From 4 to 148
mid_pos = ux_avg.shape[0] // 2

ux_vals = ux_avg[wall_pos:mid_pos] / u_fric
x_vals = np.arange(len(ux_vals)) * yp
import matplotlib.pyplot as plt

fig, ax = plt.subplots()
fig.set_size_inches(6, 5)

ax.plot(df_cp["ux_avg"]["y+"], df_cp["ux_avg"]["u/u*"], "ko", fillstyle="none")
ax.plot(x_vals, ux_vals, "k--")
ax.legend(["Experimental", "Nassu"])

ax.set_xscale("symlog")
ax.set_xlim((1, 170))

ax.set_ylabel("$u/u*$")
ax.set_xlabel("$y^+$")

plt.show(fig)
../../../_images/f6ff76082c49a91b9961a3e649ac5721f5288f78541a0caecfb071bf5743d818.png

The average flow velocity shown below presents an excellent agreement with experimental results. Below, the root mean squared velocity \(u_{\alpha,\mathrm{rms}}\) is also presented against experimental data.

import matplotlib.pyplot as plt

fig, ax = plt.subplots()
fig.set_size_inches(6, 5)

get_prof_plot = lambda arr: arr[wall_pos:mid_pos] / u_fric

ax.plot(df_cp["ux_rms"]["y+"], df_cp["ux_rms"]["u/u*"], "ko", fillstyle="none")
ax.plot(df_cp["ur_rms"]["y+"], df_cp["ur_rms"]["u/u*"], "ro", fillstyle="none")
ax.plot(df_cp["uang_rms"]["y+"], df_cp["uang_rms"]["u/u*"], "bo", fillstyle="none")
ax.plot(x_vals, get_prof_plot(ux_rms), "k--")
ax.plot(x_vals, get_prof_plot(uy_rms), "r--")
ax.plot(x_vals, get_prof_plot(uz_rms), "b--")
ax.legend(
    [
        r"Exp. $u_x$",
        r"Exp. $u_r$",
        r"Exp. $u_{\theta}$",
        r"Nassu $u_x$",
        r"Nassu $u_r$",
        r"Nassu $u_{\theta}$",
    ]
)

ax.set_xlim((1, 170))

ax.set_xlabel("$y^+$")

plt.show(fig)
../../../_images/55030e4f2c0cff870355df412426e67d0266b94f558051b0180492a351170c64.png

Also, an excellement agreeement is obtained for all directions in cylindrical coordinates. In general the results confirm the solver capability of solving a pipe flow turbulence, confirming the IBM as adequate to represent a curved boundary and the D3Q27 velocity set as capable of axissymetric turbulence.

Version#

sim_info = sim_cfg.output.read_info()

nassu_commit = sim_info["commit"]
nassu_version = sim_info["version"]
print("Version:", nassu_version)
print("Commit hash:", nassu_commit)
Version: 1.6.17
Commit hash: 09c9337ca174e12ce2cfa6eb39867054691a2742

Configuration#

from IPython.display import Code

Code(filename=filename)
simulations:
  - name: periodicTurbulentPipe
    save_path: ./tests/validation/results/05_turbulent_pipe_flow/periodic

    n_steps: 400000
    # u* = 0.0034013 / R = 72 / ETT = R/u* = 18,820
    # Re_tau = 180
    # y+ = 2.5

    report:
      frequency: 1000

    domain:
      domain_size:
        x: 456
        y: 152
        z: 152
      block_size: 8
      bodies:
        cylinder:
          lnas_path: fixture/lnas/basic/cylinder.lnas
          transformation:
            scale: [72, 72, 72]
            translation: [-4, 4, 4]

    data:
      divergence: { frequency: 50 }
      instantaneous:
        default: { interval: { frequency: 200000 }, macrs: [rho, u] }
      statistics:
        interval: { frequency: 100, start_step: 200000 }
        macrs_1st_order: [rho, u]
        macrs_2nd_order: [u]
        exports:
          default: { interval: { frequency: 50000 } }

    models:
      precision:
        default: single

      LBM:
        tau: 0.504081632653061
        F:
          x: 3.21368E-07
          y: 0
          z: 0
        vel_set: D3Q27
        coll_oper: RRBGK
      initialization:
        vtm_filename: "../nassuArtifacts/macrs/turbulent_pipe.vtm"

      engine:
        name: CUDA

      BC:
        periodic_dims: [true, false, false]
        BC_map:
          - pos: N
            BC: RegularizedHWBB
            wall_normal: N
            order: 1

          - pos: S
            BC: RegularizedHWBB
            wall_normal: S
            order: 1

          - pos: F
            BC: RegularizedHWBB
            wall_normal: F
            order: 2

          - pos: B
            BC: RegularizedHWBB
            wall_normal: B
            order: 2

      IBM:
        forces_accomodate_time: 1000
        body_cfgs:
          default: {}