Flow Over Cylinder

Flow Over Cylinder#

The simulation of a high Reynolds flow around a circular cylinder is used as validation for the HRRBGK collision operator implementation, the case is the same used in Jacob et al., 2018. The dynamic implementation of the tuning parameter is expected to provide more accurate results of velocity profiles for the HRRBGK in comparison to RRBGK.

from nassu.cfg.model import ConfigScheme

filename = "tests/validation/cases/09_flow_over_cylinder.nassu.yaml"

sim_cfgs = ConfigScheme.sim_cfgs_from_file_dct(filename)

The computational domain is periodic at \(y\)-direction, uses free slip BCs in \(z\)-direction, uniform velocity at inlet and Neumann with constant pressure at outlet.

Load experimental values for comparison:

import pandas as pd
import pathlib

comparison_folder = "tests/validation/comparison/Flow_over_cylinder"
files = [
    "ux_avg_upstream",
    "ux_rms_upstream",
    "ux_avg_102D",
    "ux_rms_102D",
    "uz_avg_102D",
    "uz_rms_102D",
    "ux_avg_154D",
    "ux_rms_154D",
    "uz_avg_154D",
    "uz_rms_154D",
    "ux_avg_202D",
    "ux_rms_202D",
    "uz_avg_202D",
    "uz_rms_202D",
]
get_filename_csv = lambda f: pathlib.Path(comparison_folder) / (f + ".csv")


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

Results#

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

u_ref = 0.0585


def add_pos_to_points(df_points: pd.DataFrame, start_pos: float, end_pos: float):
    df_points["pos"] = np.linspace(start_pos, end_pos, num=len(df_points), endpoint=True)


def add_stats_to_points(df_points: pd.DataFrame, df_hs: dict[str, pd.DataFrame]):
    df_data = {k: df.drop(columns=["time_step"]) for k, df in df_hs.items()}
    for u_name in ["ux", "uy", "uz"]:
        df_points[f"{u_name}_avg"] = (df_data[u_name].mean() / u_ref).to_numpy(dtype=np.float32)
        df_points[f"{u_name}_rms"] = ((df_data[u_name].std() ** 2) / (u_ref**2)).to_numpy(
            np.float32
        )


def read_hs(sim_cfg: SimulationConfigs, line_name: str):
    line_ref = sim_cfg.output.series["default"].lines[line_name]
    df_points = pd.read_csv(line_ref.points_filename)
    df_hs = {u: line_ref.read_full_data(u) for u in ["ux", "uy", "uz"]}
    return (line_ref, df_points, df_hs)

Read experimental lines and add data

lines = {
    "upstream": {"start_pos": 0.5, "end_pos": 6.5},
    "line_102": {"start_pos": -1.5, "end_pos": 1.5},
    "line_154": {"start_pos": -1.5, "end_pos": 1.5},
    "line_202": {"start_pos": -1.5, "end_pos": 1.5},
}


def get_lines_from_sim(sim_cfg: SimulationConfigs):
    dct_sim_cfg = {}
    for line_name, dct_line in lines.items():
        hs, df_points, df_hs = read_hs(sim_cfg, line_name)
        # Filter time steps
        df_hs = {u: df[df["time_step"] > 6000] for u, df in df_hs.items()}
        # df_hs = df_hs[df_hs["time_step"] < 10000]

        add_pos_to_points(df_points, dct_line["start_pos"], dct_line["end_pos"])
        add_stats_to_points(df_points, df_hs)
        dct_sim_cfg[line_name] = {"hs": hs, "df_points": df_points}
    return dct_sim_cfg


sim_cfgs_lines = [get_lines_from_sim(sim_cfg) for sim_cfg in sim_cfgs.values()]

Set styles and legends for plots

lg = [f"{s.models.LBM.vel_set} {s.models.LBM.coll_oper}" for s in sim_cfgs.values()]

num_styles = ["-k", "-r", "-b", "-m", "-c"]
exp_style = "ko"
def plot_line_values(ax, line_name: str, vel_name: str, exp_filename: str):
    for i, sim_lines in enumerate(sim_cfgs_lines):
        df_points = sim_lines[line_name]["df_points"]
        ax.plot(df_points["pos"], df_points[vel_name], num_styles[i])
    df_exp = df_vel[exp_filename]
    ax.plot(df_exp.iloc[:, 0], df_exp.iloc[:, 1], exp_style, fillstyle="none")
    ax.legend(lg + ["Exp."])
import matplotlib.pyplot as plt

fig, ax = plt.subplots(1, 2)
fig.set_size_inches(12, 5)

plot_line_values(ax[0], "upstream", "ux_avg", "ux_avg_upstream")
ax[0].set_ylabel("$u_{\mathrm{avg}}/U_{0}$")
ax[0].set_xlabel("$x/D$")


plot_line_values(ax[1], "upstream", "ux_rms", "ux_rms_upstream")
ax[1].set_ylabel("$u_{\mathrm{rms}}/U_{0}$")
ax[1].set_xlabel("$x/D$")

plt.show(fig)
../../../_images/aec13599d82566fa1f19e620e021585fff5c70f651acb35a9ef2c8c6fc7badcf.png
fig, ax = plt.subplots(3, 2)
fig.set_size_inches(12, 18)

for i, dist in enumerate([102, 154, 202]):
    plot_line_values(ax[i][0], f"line_{dist}", "ux_avg", f"ux_avg_{dist}D")
    ax[i][0].set_ylabel("$u_{\mathrm{avg}}/U_{0}$")
    ax[i][0].set_xlabel("$z/D$")
    ax[i][0].set_title(f"{dist/100:.2f}D $u$")

    plot_line_values(ax[i][1], f"line_{dist}", "ux_rms", f"ux_rms_{dist}D")
    ax[i][1].set_ylabel("$u_{\mathrm{rms}}/U_{0}$")
    ax[i][1].set_xlabel("$z/D$")
    ax[i][1].set_title(f"{dist/100:.2f}D $u$")

plt.show(fig)
../../../_images/f05455c1d18bfdca856eb84b0e411e8217c8710e9e2edccd7fee56503ad30cac.png
fig, ax = plt.subplots(3, 2)
fig.set_size_inches(12, 18)

for i, dist in enumerate([102, 154, 202]):
    plot_line_values(ax[i][0], f"line_{dist}", "uz_avg", f"uz_avg_{dist}D")
    ax[i][0].set_ylabel("$w_{\mathrm{avg}}/U_{0}$")
    ax[i][0].set_xlabel("$z/D$")
    ax[i][0].set_title(f"{dist/100:.2f}D $w$")

    plot_line_values(ax[i][1], f"line_{dist}", "uz_rms", f"uz_rms_{dist}D")
    ax[i][1].set_ylabel("$w_{\mathrm{rms}}/U_{0}$")
    ax[i][1].set_xlabel("$z/D$")
    ax[i][1].set_title(f"{dist/100:.2f}D $w$")

plt.show(fig)
../../../_images/10f44f80a3a2933e6fccad5bb2cc5733127a6d189da69518c77a8ef4c8b36f73.png

Version#

sim_cfg = next(iter(sim_cfgs.values()))
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: cab6b7ad2a955ba7656e05b10c2cee0c8f0fdad4

Configuration#

from IPython.display import Code

Code(filename=filename)
simulations:
  - name: flowOverCylinder
    save_path: ./tests/validation/results/09_flow_over_cylinder

    n_steps: 20000

    report: { frequency: 100 }

    domain:
      domain_size:
        x: 160
        y: 8
        z: 80
      block_size: 8
      bodies:
        cylinder:
          IBM:
            run: True
            cfg_use: cylinder_cfg
          lnas_path: fixture/lnas/basic/cylinder_refined.lnas
          small_triangles: "add"
          area: { min: 0.25, max: 1.0 }
          transformation:
            scale: [1.0, 1.0, 1.0]
            translation: [23.75, -24, 38.75]
      refinement:
        static:
          default:
            volumes_refine:
              - start: [20, 0, 16]
                end: [160, 8, 64]
                lvl: 1
                is_abs: true
              - start: [20, 0, 24]
                end: [80, 8, 56]
                lvl: 2
                is_abs: true
              - start: [20, 0, 32]
                end: [48, 8, 48]
                lvl: 3
                is_abs: true
              - start: [20, 0, 36]
                end: [34, 8, 44]
                lvl: 4
                is_abs: true
              - start: [23, 0, 38]
                end: [30, 8, 42]
                lvl: 5
                is_abs: true

    data:
      divergence: { frequency: 10 }
      instantaneous:
        default: { interval: { frequency: 1000 }, macrs: [rho, u, omega_LES, sigma] }
      export_IBM_nodes: { frequency: 5000 }
      probes:
        historic_series:
          default:
            macrs: ["rho", "u"]
            interval: { frequency: 10, lvl: 5 }
            lines:
              upstream:
                dist: 0.25
                start_pos: [26.25, 4, 40]
                end_pos: [41.25, 4, 40]
              line_102:
                dist: 0.25
                start_pos: [27.65, 4, 36.25]
                end_pos: [27.65, 4, 43.75]
              line_154:
                dist: 0.25
                start_pos: [28.85, 4, 36.25]
                end_pos: [28.85, 4, 43.75]
              line_202:
                dist: 0.25
                start_pos: [30.05, 4, 36.25]
                end_pos: [30.05, 4, 43.75]
        spectrum_analysis:
          macrs: ["rho", "u"]
          points:
            probe:
              pos: [32.5, 4, 40]

    models:
      precision:
        default: single

      LBM:
        tau: 0.5001125
        vel_set: !unroll [D3Q27, D3Q27, D3Q19]
        coll_oper: !unroll [RRBGK, HRRBGK, HRRBGK]

      initialization:
        rho: 1.0
        u:
          x: 0.0585
          y: 0
          z: 0

      engine:
        name: CUDA

      BC:
        periodic_dims: [false, true, false]
        BC_map:
          - pos: F
            BC: Neumann
            wall_normal: F
            order: 1

          - pos: B
            BC: Neumann
            wall_normal: B
            order: 1

          - pos: E
            BC: RegularizedNeumannOutlet
            rho: 1.0
            wall_normal: E
            order: 2

          - pos: W
            BC: UniformFlow
            wall_normal: W
            ux: 0.0585
            uy: 0
            uz: 0
            rho: 1
            order: 2

          - pos: NF
            BC: Neumann
            wall_normal: F
            order: 1

          - pos: NB
            BC: Neumann
            wall_normal: B
            order: 1

          - pos: SF
            BC: Neumann
            wall_normal: F
            order: 1

          - pos: SB
            BC: Neumann
            wall_normal: B
            order: 1

      IBM:
        dirac_delta: 3_points
        forces_accomodate_time: 0
        reset_forces: true
        body_cfgs:
          cylinder_cfg:
            n_iterations: 3
            forces_factor: 0.5
            wall_model:
              name: EqTBL
              dist_ref: 3.125
              dist_shell: 0.125
              start_step: 0
              params:
                z0: 0.00155
                TDMA_max_error: 1e-04
                TDMA_max_iters: 10
                TDMA_n_div: 25

      multiblock:
        overlap_F2C: 2

      LES:
        model: Smagorinsky
        sgs_cte: 0.17