Poiseuille Channel (Multilevel)

Poiseuille Channel (Multilevel)#

In this simulation, the multilevel approach is tested to check the macroscopics continuity when passing through different levels and when applied at boundary regions of the computational domain.

from nassu.cfg.model import ConfigScheme

filename = "tests/validation/cases/02_poiseuille_channel_flow.nassu.yaml"

sim_cfgs = ConfigScheme.sim_cfgs_from_file_dct(filename)

In this case, a small value for the relaxation time \(\tau\) was adopted. Since the aim is to check possible discontinuities in the multilevel formulation, less stable values of \(\tau\) could highlight implementation issues.

sim_cfg = next(
    sim_cfg
    for (name, _), sim_cfg in sim_cfgs.items()
    if name == "velocityNeumannPoiseuilleChannelMultilevel"
)

Extract data from multiblock data from output file of macrs export

import numpy as np
from vtk.util.numpy_support import vtk_to_numpy
from tests.validation.notebooks import common

extracted_data = {}
array_to_extract = "ux"

export_instantaneous_cfg = sim_cfg.output.instantaneous
macr_export = export_instantaneous_cfg["default"]
time_step = macr_export.time_steps(sim_cfg.n_steps)[-1]
reader = macr_export.read_vtm_export(time_step)

p1 = [sim_cfg.domain.domain_size.x * 0.75, 0.5, 0]
p2 = [sim_cfg.domain.domain_size.x * 0.75, sim_cfg.domain.domain_size.y - 0.5, 0]
line = common.create_line(p1, p2, sim_cfg.domain.domain_size.y - 1)

# Get the points from the vtkLineSource
polyData = line.GetOutput()
points = polyData.GetPoints()

pos = np.linspace(p1, p2, sim_cfg.domain.domain_size.y)

probe_filter = common.probe_over_line(line, reader.GetOutput())

probed_data = vtk_to_numpy(probe_filter.GetOutput().GetPointData().GetArray(array_to_extract))
extracted_data[(sim_cfg.sim_id, sim_cfg.name)] = {"pos": pos, "data": probed_data}

extracted_data.keys()
dict_keys([(0, 'velocityNeumannPoiseuilleChannelMultilevel')])

Extract data from multiblock data from output file of macrs export for rho plotting

average_data = {}
array_to_extract = "rho"

for time_step in macr_export.interval.get_all_process_steps(sim_cfg.n_steps):
    reader = macr_export.read_vtm_export(time_step)

    multiblock_dataset = reader.GetOutput()
    n_blocks = multiblock_dataset.GetNumberOfBlocks()

    blocks_data = []
    for i in range(n_blocks):
        block = multiblock_dataset.GetBlock(i)
        data_array = vtk_to_numpy(block.GetCellData().GetArray(array_to_extract))
        blocks_data.append(data_array)
    multiblock_avg = np.average(np.concatenate(blocks_data).flatten())

    average_data[(sim_cfg.sim_id, sim_cfg.name, time_step)] = multiblock_avg

average_data
{(0, 'velocityNeumannPoiseuilleChannelMultilevel', 0): 1.0,
 (0, 'velocityNeumannPoiseuilleChannelMultilevel', 12000): 1.0145637,
 (0, 'velocityNeumannPoiseuilleChannelMultilevel', 24000): 1.0145637,
 (0, 'velocityNeumannPoiseuilleChannelMultilevel', 36000): 1.0145637,
 (0, 'velocityNeumannPoiseuilleChannelMultilevel', 48000): 1.0145637,
 (0, 'velocityNeumannPoiseuilleChannelMultilevel', 60000): 1.0145637,
 (0, 'velocityNeumannPoiseuilleChannelMultilevel', 64000): 1.0145637}

Extract data from multiblock data from output file of macrs export for profile plotting

profile_data = {}
array_to_extract = "ux"

time_step = macr_export.time_steps(sim_cfg.n_steps)[-1]
reader = macr_export.read_vtm_export(time_step)

# p1 = [0.5, sim_cfg.domain.domain_size.y / 2 - 0.5, bounds_z[0]]
# p2 = [sim_cfg.domain.domain_size.x - 0.5, sim_cfg.domain.domain_size.y / 2 - 0.5, bounds_z[1]]
# line = create_line(p1, p2, sim_cfg.domain.domain_size.x - 1)
p1 = [0.25, sim_cfg.domain.domain_size.y / 2, 0]
p2 = [sim_cfg.domain.domain_size.x - 0.25, sim_cfg.domain.domain_size.y / 2, 0]
line = common.create_line(p1, p2, 2 * sim_cfg.domain.domain_size.x - 1)

# Get the points from the vtkLineSource
polyData = line.GetOutput()
points = polyData.GetPoints()

pos = np.linspace(p1, p2, sim_cfg.domain.domain_size.x * 2)

probe_filter = common.probe_over_line(line, reader.GetOutput())

probed_data = vtk_to_numpy(probe_filter.GetOutput().GetPointData().GetArray(array_to_extract))
profile_data[(sim_cfg.sim_id, sim_cfg.name)] = {"pos": np.array(pos), "data": probed_data}

profile_data.keys()
dict_keys([(0, 'velocityNeumannPoiseuilleChannelMultilevel')])

Results#

The velocity profile at the end of simulation is compared with the steady state analytical solution below:

Processing functions for Poiseuille Flow case

from typing import Callable


def get_poiseuille_analytical_func() -> Callable:
    """Poiseuille analytical velocity function

    Returns:
        Callable: Analytical velocity function
    """
    return lambda pos: 6 * (pos - pos**2)


def plot_analytical_poiseuille_vels(ax):
    x = np.arange(0, 1.01, 0.01)
    analytical_func = get_poiseuille_analytical_func()
    analytical_data = analytical_func(x)
    ax.plot(x, analytical_data, "--k", label="Analytical")
import matplotlib.pyplot as plt

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

style_num = ["o", "^", "s", "+", "x"]

num_data = extracted_data[(sim_cfg.sim_id, sim_cfg.name)]
num_avg_vel = np.average(num_data["data"])
position_vector = (num_data["pos"][:, 1] - 0.5) / (sim_cfg.domain.domain_size.y - 1)
ax.plot(
    position_vector,
    num_data["data"] / num_avg_vel,
    style_num[0],
    label=f"N={sim_cfg.domain.domain_size.x} (avg. {num_avg_vel:.2e})",
    fillstyle="none",
    c="k",
)

plot_analytical_poiseuille_vels(ax)
ax.set_title(
    f"Poiseuille (Multilevel)\n({sim_cfg.models.LBM.vel_set} {sim_cfg.models.LBM.coll_oper})"
)
ax.legend()
plt.show(fig)
../../../_images/4c06a704fe67fb25c16f3f1e74c7f67d8180f28631f902ecc312ea06f5da63ea.png

No discontinuities issues are seen through the velocity profile, indicating a satisfactory implementation.

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

plotting_data: list[float] = []
axis_data: list[int] = []

for (_, _, timestep), average_val in average_data.items():
    plotting_data.append(average_val)
    axis_data.append(timestep)

ax.plot(axis_data, plotting_data, "ok", label=f"Average {array_to_extract}", fillstyle="none")
ax.legend()
plt.show(fig)
../../../_images/08dd4f5ab37d0c10ed24d05478bf6ff1ebdef9b24ca6fb982a371a83de664677.png

The average pressure sustains the convergence aspect exhibited in the single level simulation, though it takes longer to converge since a smaller value of \(\tau\) was adopted.

import pyvista as pv

array_to_inspect = "rho"

plotter = pv.Plotter(window_size=(800, 400))

time_step = macr_export.time_steps(sim_cfg.n_steps)[-1]
multiblock_file = macr_export.time_step_filename(time_step, ".vtm")
mesh = pv.read(multiblock_file)
mesh.set_active_scalars(array_to_inspect)
plotter.add_mesh(mesh, cmap="coolwarm")

plot_title = f"{array_to_inspect} profile Poiseuille (Multilevel)\n({sim_cfg.models.LBM.vel_set} {sim_cfg.models.LBM.coll_oper})"
plotter.add_text(plot_title, position="upper_right", font_size=18, color="black")
plotter.camera_position = [(48.0, 12.0, -192.16548199848856), (48.0, 12.0, 1.0), (0.0, -1.0, 0.0)]
plotter.camera.zoom(2)
plotter.show(jupyter_backend="static")
# plotter.show(jupyter_backend='static', cpos="xy", return_cpos=True) # Trick to get automatically the camera position
../../../_images/64548fce55418743064347b94eb5d4c7532b642fb643c98070cf43ad35b8f0d8.png

The general flow’s density profile shown above presents no visible disconuity.

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

num_data = profile_data[(sim_cfg.sim_id, sim_cfg.name)]
position_vector = (num_data["pos"][:, 0] - 0.5) / (sim_cfg.domain.domain_size.x - 1)
ax.plot(position_vector, num_data["data"], c="k")

ax.set_title(
    f"Ux profile Poiseuille (Velocity-Neumann) ({sim_cfg.models.LBM.vel_set} {sim_cfg.models.LBM.coll_oper})"
)
plt.show(fig)
../../../_images/588f25e88e8386f6484b42f99f13b9ca5b372800b39c21a8542cc2975e211839.png

The centerline velocity has an asympotic profile. However, it would take a longer length for the velocity to completely stabilizes.

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: 03afae00d1166dcb73bd88413bb3cfda7b6c686c

Configuration#

from IPython.display import Code

Code(filename=filename)
simulations:
  - name: periodicPoiseuilleChannel
    save_path: ./tests/validation/results/02_poiseuille_channel_flow/periodic

    n_steps: !unroll [250, 1000, 4000, 16000]

    report:
      frequency: 1000

    domain:
      domain_size:
        x: !unroll [4, 8, 16, 32]
        y: !unroll [4, 8, 16, 32]
      block_size: !unroll [4, 8, 8, 8]

    data:
      divergence: { frequency: 50 }
      instantaneous:
        default: { interval: { frequency: 0 }, macrs: [rho, u] }
      statistics:
        interval: { frequency: 0 }

    models:
      precision:
        default: single

      LBM:
        tau: 0.9
        vel_set: D2Q9
        coll_oper: RRBGK
        F:
          # FX is divided by 8
          x: !unroll [4.0e-4, 5.0e-5, 6.25e-6, 7.8125e-07]
          y: 0

      multiblock:
        overlap_F2C: 1

      engine:
        name: CUDA

      BC:
        periodic_dims: [true, false]
        BC_map:
          - pos: N
            BC: HWBB
            wall_normal: N

          - pos: S
            BC: HWBB
            wall_normal: S

  - name: regularizedPeriodicPoiseuilleChannel
    parent: periodicPoiseuilleChannel

    models:
      LBM:
        coll_oper: RRBGK

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

          - pos: S
            BC: RegularizedHWBB
            wall_normal: S

  - name: velocityNeumannPoiseuilleChannel
    parent: periodicPoiseuilleChannel

    save_path: ./tests/validation/results/02_poiseuille_channel_flow/velocity_neumann

    report: { frequency: 1000 }

    n_steps: 64000
    domain:
      domain_size:
        x: 256
        y: 32
      block_size: 8

    data:
      instantaneous:
        default: { interval: { frequency: 16000 }, macrs: [rho, u] }

    models:
      LBM: !not-inherit
        tau: 0.9
        vel_set: D2Q9
        coll_oper: RRBGK

      BC:
        periodic_dims: [false, false]
        BC_map:
          - pos: W
            BC: UniformFlow
            wall_normal: W
            rho: 1.0
            ux: 0.05
            uy: 0
            uz: 0
            order: 1

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

          - pos: N
            BC: RegularizedHWBB
            wall_normal: N
            order: 0

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

  - name: velocityNeumannPoiseuilleChannelMultilevel
    parent: periodicPoiseuilleChannel

    save_path: ./tests/validation/results/02_poiseuille_channel_flow/multilevel

    n_steps: 64000

    domain:
      domain_size:
        x: 96
        y: 24
      block_size: 8
      refinement:
        static:
          default:
            volumes_refine:
              - { start: [0, 0], end: [8, 8], lvl: 1, is_abs: true }
              - { start: [0, 16], end: [8, 24], lvl: 1, is_abs: true }
              - { start: [8, 8], end: [24, 16], lvl: 1, is_abs: true }
              - { start: [16, 0], end: [32, 8], lvl: 1, is_abs: true }
              - { start: [24, 16], end: [48, 24], lvl: 1, is_abs: true }
              - { start: [40, 8], end: [48, 16], lvl: 1, is_abs: true }
              - { start: [56, 0], end: [72, 8], lvl: 1, is_abs: true }
              - { start: [80, 8], end: [88, 16], lvl: 1, is_abs: true }
              - { start: [88, 16], end: [96, 24], lvl: 1, is_abs: true }

    data:
      divergence: { frequency: 50 }
      instantaneous:
        default: { interval: { frequency: 12000 }, macrs: [rho, u, S] }

    models:
      precision:
        default: single

      LBM: !not-inherit
        tau: 0.8
        vel_set: D2Q9
        coll_oper: RRBGK

      engine:
        name: CUDA

      BC:
        periodic_dims: [false, false, true]
        BC_map:
          - pos: W
            BC: UniformFlow
            rho: 1.0
            ux: 0.05
            uy: 0
            uz: 0
            order: 1

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

          - pos: N
            BC: RegularizedHWBB
            wall_normal: N
            order: 0

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