Download this testcase.

Three-Dimensional Cylindrical Poiseuille Flow

This test reproduces a 3D Poiseuille flow inside a cylindrical pipe using MigFlow. The cylindrical geometry is generated with Gmsh through OpenCASCADE, and a parabolic velocity profile is prescribed at the inlet. The objective is to illustrate how to set up a 3D viscous flow simulation with physical boundaries and TFEM (P2/P1) discretization.

Keywords

FEM, FEM, Poiseuille flow

Description

This example demonstrates: - How to generate a 3D cylindrical domain using Gmsh. - How to assign physical boundary groups for inlet, outlet, and walls. - How to impose an analytical Poiseuille inflow profile. - How to solve the 3D Stokes/Navier–Stokes equations using MigFlow. - How to write MIG output for post-processing.

from migflow import fluid
import numpy as np
import os, sys, shutil
import gmsh

Output Directory

Create a fresh output directory for simulation data.

outputdir = "output" if len(sys.argv) < 2 else sys.argv[1]
shutil.rmtree(outputdir, ignore_errors=True)
os.makedirs(outputdir)

Geometry

The domain is a cylinder of: - radius r = h/2 - length w - axis oriented along the x-direction

h = 1  # domain height (diameter)
r = h / 2  # radius
w = 2  # cylinder length
axis = [w, 0, 0]  # cylinder axis direction
origin = [0, 0, 0]

Mesh Generation

  • Physical groups auto-detected from Gmsh entities:
    “Left” : inlet plane “Right” : outlet plane “Lateral”: cylindrical wall “Domain” : volume
  • Uniform mesh size set by callback mesh_size = r/2.
mesh_size = r / 2  # characteristic mesh size

gmsh.model.occ.add_cylinder(origin[0], origin[1], origin[2], *axis, r)
gmsh.model.occ.synchronize()

# Assign physical groups automatically retrieved from CAD IDs:
gmsh.model.add_physical_group(2, [1], name="Lateral")
gmsh.model.add_physical_group(2, [2], name="Right")
gmsh.model.add_physical_group(2, [3], name="Left")
gmsh.model.add_physical_group(3, [1], name="Domain")

# Set uniform mesh size
gmsh.model.mesh.set_size_callback(lambda dim, tag, x, y, z, lc: mesh_size)

# Generate tetrahedral 3D mesh
gmsh.model.mesh.generate(3)

Physical Parameters

g = np.array([0.0, 0.0, 0.0])  # no gravity
mu = 1  # viscosity
rho = 1  # density
dp = 1  # driving pressure parameter (for U_max estimation)

Fluid Problem Initialization

f = fluid.FluidProblem(3, g, mu, rho, p2p1=True)
f.load_msh(None)

Physical Setup

The analytical Poiseuille velocity profile inside a cylindrical pipe is:

u_x(r) = U_max * (1 - (r/R)^2)

where: - r = radial distance to cylinder axis - R = cylinder radius - U_max derived from dp, μ, geometry

u_max = dp / w * r**2 / (20 * mu)


def u_in(x):
    """Analytical Poiseuille inflow profile projected on velocity x-component."""
    radial_dist = np.linalg.norm(x[:, 1:], axis=1)
    return u_max * (1 - (radial_dist / r) ** 2)

Boundary conditions

  • Left : inflow with Poiseuille profile
  • Right : open boundary (p = 0)
  • Lateral: no-slip (zero velocity)
f.set_wall_boundary("Lateral", velocity=np.array([0.0, 0.0, 0.0]))
f.set_open_boundary("Right", pressure=0)
f.set_open_boundary("Left", velocity=[u_in, 0.0, 0.0])

Simulation Parameters

t = 0
i = 0
dt = 1e-2
tEnd = 10 * dt
outf = 1

Time Integration Loop

while t < tEnd:
    print(f"{i:4d}, {t:.6g}/{tEnd:.6g}, dt={dt:.6g}")

    # Write output every `outf` iterations
    if i % outf == 0:
        f.write_mig(outputdir, t)

    # Implicit Euler solver
    f.implicit_euler(dt, check_residual_norm=1)

    t += dt
    i += 1