Structured Rectangular Meshes

dream.mesh.get_rectangular_mesh builds a structured Cartesian mesh from explicit nodal coordinate arrays. The caller supplies one 1-D array per spatial direction for each domain region; the function takes their union as the global set of mesh nodes and connects them into quad (default) or triangle elements.

Boundaries are specified the same way: passing a single-valued array in one direction selects all nodes on that edge, while the other direction spans its full extent.

The helper get_nodal_points generates 1-D node distributions with several clustering options ('uniform', 'cosine', 'tanh', 'exponential') that concentrate grid points near boundaries.

[1]:
import numpy as np
from ngsolve.webgui import Draw
from dream.mesh import get_rectangular_mesh, get_nodal_points

Uniform channel mesh

We first generate a simple channel \([0, 4] \times [-0.5, 0.5]\) with uniform node spacing in both directions and four named boundaries.

[2]:
nx = np.linspace(0.0, 4.0, 21)   # 20 uniform intervals in x
ny = np.linspace(-0.5, 0.5, 11)  # 10 uniform intervals in y

domains = [
    ("channel", (nx, ny)),
]
boundaries = [
    ("inflow",  (np.array([0.0]), ny)),
    ("outflow", (np.array([4.0]), ny)),
    ("wall",    (nx, np.array([-0.5]))),
    ("wall",    (nx, np.array([0.5]))),
]

mesh = get_rectangular_mesh(domains, boundaries)

print("Materials: ", mesh.GetMaterials())
print("Boundaries:", mesh.GetBoundaries())

Draw(mesh)
Materials:  ('channel',)
Boundaries: ('inflow', 'outflow', 'wall')
[2]:
WebGLScene

Wall-clustered channel mesh

For boundary-layer-resolving simulations it is useful to cluster nodes near the walls. Here we use get_nodal_points with a hyperbolic-tangent distribution and stretching factor \(\beta = 3\) to concentrate the grid points near \(y = \pm 0.5\).

[3]:
nx = np.linspace(0.0, 4.0, 21)
# tanh distribution in [0, 1], mapped to [-0.5, 0.5]
ny = get_nodal_points(21, distribution='tanh', beta=3) - 0.5

domains = [
    ("channel", (nx, ny)),
]
boundaries = [
    ("inflow",  (np.array([0.0]), ny)),
    ("outflow", (np.array([4.0]), ny)),
    ("wall",    (nx, np.array([ny[0]]))),
    ("wall",    (nx, np.array([ny[-1]]))),
]

mesh = get_rectangular_mesh(domains, boundaries)

print("Materials: ", mesh.GetMaterials())
print("Boundaries:", mesh.GetBoundaries())

Draw(mesh)
Materials:  ('channel',)
Boundaries: ('inflow', 'outflow', 'wall')
[3]:
WebGLScene

Two-domain channel with a downstream sponge region

Multi-domain meshes are constructed by passing several entries to domains. The node arrays from all domains are merged, so the two regions share their common boundary at \(x = 4\). We use a coarser spacing in the downstream sponge zone.

Both wall boundaries span the full channel length, so we pass the union of the x-coordinate arrays from both domains.

[4]:
nx_channel = np.linspace(0.0, 4.0, 41)  # fine grid in main channel
nx_sponge  = np.linspace(4.0, 5.0, 6)  # coarser grid in sponge region
nx_all     = np.union1d(nx_channel, nx_sponge)
ny         = np.linspace(-0.5, 0.5, 11)

domains = [
    ("channel", (nx_channel, ny)),
    ("sponge",  (nx_sponge,  ny)),
]
boundaries = [
    ("inflow",  (np.array([0.0]), ny)),
    ("outflow", (np.array([5.0]), ny)),
    ("wall",    (nx_all, np.array([-0.5]))),
    ("wall",    (nx_all, np.array([0.5]))),
]

mesh = get_rectangular_mesh(domains, boundaries)

print("Materials: ", mesh.GetMaterials())
print("Boundaries:", mesh.GetBoundaries())

Draw(mesh)
Materials:  ('channel', 'sponge')
Boundaries: ('inflow', 'outflow', 'wall')
[4]:
WebGLScene