Home / Blogs / Numerical Methods and Stability

The Complete CFD Meshing Guide

Share 𝕏 in
The Complete CFD Meshing Guide

The uncomfortable truth: 80% of your simulation’s success depends on mesh quality. Most engineers spend 20% of their time on it. This guide fixes that.



1. The Real Cost of Getting Meshing Wrong

Meshing errors don’t always announce themselves with a crash. Often they announce themselves six months later, in a meeting room, when someone asks why your CFD results don’t match the test rig.

A real-world case:

A major gas turbine manufacturer spent six months debugging apparent “solver instability” in their compressor simulations. The actual culprit was a 0.5 mm gap at the inlet that was silently removed during CAD cleanup — flagged as cosmetic by their preprocessing script. That tiny feature controlled boundary layer transition. Its removal caused a 15% efficiency mismatch between CFD and experimental data.

Six months. One half-millimetre.

The practical upshot:

Budget 40% of your project time for meshing and verification.
This upfront investment typically saves 200% in downstream troubleshooting.

This is not an exaggeration. Garbage in, garbage out applies to CFD more brutally than almost any other engineering discipline, because the garbage can look convincing — residuals converging, colorful flow fields, plausible-looking pressure distributions — right up until someone runs the experiment.


2. The Seven Deadly Meshing Sins

Sin 1 — The “Faster is Better” Fallacy

The scenario: You’re under deadline pressure. You build a 200,000-cell mesh instead of 2 million. The simulation runs in 2 hours instead of 20. Residuals converge cleanly. You write up your report. And you’re completely wrong.

Why it fails: Flow physics happen at specific scales. Coarse meshes don’t just give less accurate results — they give different physics. Separation points shift. Wakes change topology. Pressure recovery doesn’t happen.

The Numbers: Sedan Aerodynamics Case Study

Mesh Resolution Cell Count Drag Coefficient (C_d) Error vs. Fine Compute Time
Ultra-Coarse (2 m elements) 250,000 0.312 +27% 0.5 hrs
Coarse (1 m) 1,000,000 0.283 +16% 2 hrs
Medium (0.5 m) 4,000,000 0.245 +3% 8 hrs
Fine (0.3 m) 12,000,000 0.238 Baseline 24 hrs

Key observations:
– The medium mesh (8 hours) gives only 3% error versus the fine mesh (24 hours). This is the sweet spot for most engineering work.
– The coarse mesh runs fast but is wrong by 16% — worse than many experimental uncertainties.
– Ultra-coarse is in a different reality entirely.

The Fix: Goal-Driven Adaptive Refinement

Don’t try to guess the right mesh upfront. Use a structured iterative approach:

Step 1 — Start deliberately coarse.
Capture geometry correctly but don’t worry about resolution yet.

Step 2 — Identify high-gradient regions.
Run an initial simulation and locate where flow quantities change rapidly.

# Identify zones requiring refinement
gradient_threshold = 0.2 * max_velocity  # 20% of max velocity
refinement_zones = where(abs(velocity_gradient) > gradient_threshold)
# Typical locations: boundary layers, separation points, vortex cores

Step 3 — Refine iteratively.
Increase cell count by 30–50% per iteration, focused on the regions identified above.

Step 4 — Stop when outputs converge.
Continue until key outputs (drag, pressure drop, heat flux) change less than 2% between successive meshes.

Pro tip — solution-adaptive meshing in OpenFOAM:

# In system/controlDict
adaptation
{
    type            meshRefinement;
    field           U;
    lowerRefineLevel 0.1;
    upperRefineLevel 0.9;
    nBufferLayers   1;
}

Sin 2 — The Boundary Layer Blunder

The problem: y⁺ is the most misunderstood number in CFD. Get it wrong and your drag predictions can be off by 40%, heat transfer by 50%.

The y⁺ Forbidden Zone

y⁺ Range Status What It Means
y⁺ < 5 ✅ Wall-resolved Viscous sublayer captured; use low-Re turbulence models
5 < y⁺ < 30 Forbidden Zone Too thick for wall-resolved, too thin for wall functions — both models fail here
30 < y⁺ < 300 ✅ Wall functions Logarithmic layer captured; use standard wall functions
y⁺ > 300 ⚠️ Too coarse Wall function extrapolating beyond its validity range

The forbidden zone is CFD purgatory. If your post-processing shows y⁺ values clustering between 5 and 30, you need to remesh — either push first-cell height down to y⁺ ≈ 1, or up to y⁺ ≈ 50+.

The Fix: Calculate First, Mesh Second

Never set inflation parameters by feel. Calculate the required first-cell height analytically before opening your mesher.

Full calculation procedure (turbulent flat plate, Re_x = 10⁶):

import math

# Input parameters
U_inf = 20.0      # m/s freestream velocity
rho   = 1.225     # kg/m³ air density
nu    = 1.81e-5   # m²/s kinematic viscosity
x     = 1.0       # m characteristic length

# Step 1: Estimate wall shear stress
Re_x  = U_inf * x / nu              # ~1.1 × 10⁶
Cf    = 0.0592 * Re_x**(-0.2)       # Turbulent skin friction coefficient
tau_w = 0.5 * rho * U_inf**2 * Cf  # Wall shear stress ≈ 7.1 Pa
u_tau = math.sqrt(tau_w / rho)      # Friction velocity ≈ 0.24 m/s

# Step 2: Calculate first cell height
y_plus_target = 1.0                 # For LES/DES or low-Re RANS
delta_y = (y_plus_target * nu) / u_tau
# Result: delta_y ≈ 75 μm

print(f"Re_x:          {Re_x:.2e}")
print(f"C_f:           {Cf:.4f}")
print(f"tau_w:         {tau_w:.2f} Pa")
print(f"u_tau:         {u_tau:.3f} m/s")
print(f"First cell Δy: {delta_y*1e6:.1f} μm")

Setting inflation layer parameters:

Parameter Value Notes
First cell height (Δy) 75 μm From calculation above
Growth ratio 1.2 Never exceed 1.3 for sensitive flows
Total layers 18–20
Total BL thickness ~1.3 mm Should cover entire boundary layer

Validating total BL thickness:
The inflation stack must cover the full boundary layer depth. For turbulent flow:

δ ≈ 0.37 × x × Re_x^(−0.2)

If your inflation stack is thinner than δ, you’ll have abrupt size transitions inside the boundary layer — which is as bad as no inflation at all.

Post-simulation validation checklist:
– [ ] Actual y⁺ values on all walls are within target range
– [ ] No y⁺ values fall in the forbidden zone (5–30) for your turbulence model
– [ ] Boundary layer contains ≥ 15 cells
– [ ] Growth rate is smooth — no sudden jumps visible in cross-section plots


Sin 3 — The Quality Metric Mirage

The scenario: Your mesh passes every automated check. Skewness: 0.85 ✓. Aspect ratio: 200 ✓. Orthogonal quality: 0.18 ✓. You submit. The solver diverges at iteration 47.

Why metrics lie: Quality metrics measure cell shape in isolation. They don’t know about:
Flow alignment — cells oriented perpendicular to the flow direction
Transition smoothness — sudden 10× size jumps between zones
Location criticality — one bad cell in a stagnation region is worse than 1,000 bad cells in a far-field wake

The Fix: Tightened Standards + Visual Inspection

Don’t accept your mesher’s default “acceptable” thresholds. Use engineering-grade targets:

Metric Vendor “OK” Industrial Standard Critical Threshold
Skewness < 0.95 < 0.85 > 0.90 → divergence risk
Aspect Ratio (core) < 1000 < 100 > 200 → gradient errors
Orthogonal Quality > 0.10 > 0.15 < 0.10 → interpolation fails
Growth Rate < 1.5 1.1–1.3 > 1.5 → pressure oscillations

Flow alignment check:

# Flag cells misaligned with primary flow direction
expected_flow_dir = [1, 0, 0]  # Main flow in x-direction

for cell in mesh.cells:
    cell_orientation = compute_principal_axis(cell)
    angle = angle_between(cell_orientation, expected_flow_dir)
    if angle > 45:
        flag_for_remeshing(cell)

The critical visual inspection rule:

Color your mesh by skewness. If red zones (high skewness) coincide with any of:
– Stagnation points
– Separation regions
– Vortex cores

Stop and remesh. A bad cell in a benign region is a minor inconvenience. A bad cell at a stagnation point is a simulation-ending event.


Sin 4 — The Geometry Simplification Trap

The $500,000 mistake: A pump manufacturer removed “cosmetic” 0.5 mm fillets during CAD cleanup. Their CFD predicted 92% efficiency. Their physical prototype measured 78%. Six months and $500,000 in redesign later, they discovered those fillets controlled the entire inlet flow structure.

The core misunderstanding: There is a fundamental difference between:

A small geometric feature can have a large fluid dynamic footprint. Never remove features based on physical size alone.

The Fix: Feature-Aware Meshing

Decision rule for feature preservation:

For each geometric feature:
  if (feature_size < 0.1 × domain_size) AND (feature_in_flow_path):
      RESOLVE with ≥ 3 cells across the feature
  else:
      safe to simplify

Curvature-based sizing in ANSYS Meshing:

Sizing → Curvature Normal Angle = 12°
Min Size = 0.1 mm
Max Size = 5 mm

This automatically places more cells in regions of high curvature, which usually correlates with flow-critical geometry.

Handling specific feature types:

Feature Treatment
Trailing edges (sharp) Add 0.1–0.5 mm virtual fillet
Acute corners Blend or use boundary layer collapse
Narrow gaps Ensure ≥ 3 cells across the gap
Small fillets in flow path Preserve and resolve with ≥ 3 cells
Decorative surface features away from flow Safe to remove

Before defeaturing: Run a curvature analysis. Features with high curvature that are aligned with the expected flow direction are almost always aerodynamically important.


Sin 5 — The Infinite Domain Fantasy

The problem: “2 body lengths upstream is enough.” It usually isn’t. Too-small domains create artificial blockage, alter pressure gradients, and change separation behaviour — all without triggering any solver warnings.

Quantified Impact of Domain Size (External Flow)

Domain Size Drag Error Pressure Error
2L upstream, 4L downstream 8.2% 12.5%
5L upstream, 10L downstream 2.1% 3.8%
10L upstream, 15L downstream 0.7% 1.2%

The jump from the smallest to the middle domain costs almost no extra mesh cells in the far field (where cells are large), but cuts your error by 4×.

Domain Sizing Guidelines

External aerodynamics (vehicles, aircraft, bluff bodies):

Upstream:    5–10 × characteristic length
Downstream: 10–15 × characteristic length
Sides:       5–10 × characteristic length
Top/Bottom:  5–10 × characteristic length

Internal flows (pipes, ducts, channels):

Inlet development length:  ≥ 20D  (turbulent)
                           ≥ 100D (laminar)
Outlet extension:          ≥ 5D past region of interest

Example — 5 m vehicle at 100 km/h:

Level Domain Size (L×W×H)
Minimum 50 m × 40 m × 30 m
Recommended 75 m × 50 m × 40 m
High accuracy 100 m × 75 m × 50 m

When computational budget won’t allow large domains:
Run a 3-point domain sensitivity study. If results change < 2% between the two larger domains, the smaller one is acceptable. Never skip this — “I think it’s big enough” is not an engineering justification.

On symmetry: Use symmetry boundaries only when you have genuine evidence that the flow is symmetric. Many flows that appear geometrically symmetric have asymmetric solutions (bluff body shedding, bifurcated stall cells, etc.). When in doubt, run the full domain.


Sin 6 — Interface Ignorance

The problem: Your rotating machinery simulation shows a mysterious 5% mass imbalance. The solver is “stable” but the answer is wrong. The culprit is nearly always a poorly configured interface between stationary and rotating domains.

Interface Failure Modes

Failure Mode Symptom Cause
Non-conformal without overlap Mass leaks, non-conservation Cells don’t share common faces
Size ratio > 5:1 across interface Interpolation errors accumulate Large cells interpolating into small ones
Poor AMI overlap Vorticity “lost” between zones Insufficient overlap thickness

The Fix: Interface Best Practices

For sliding meshes (rotating machinery):

Overlap thickness:   ≥ 3 cells
Size ratio:          ≤ 3:1 (coarse:fine)
Interpolation:       Conservative (not linear)
Mass flux monitor:   < 0.1% imbalance across interface

OpenFOAM rotating zone configuration:

// system/fvOptions
fan
{
    type            meanVelocityForce;
    active          yes;
    selectionMode   cellZone;
    cellZone        rotor;
    fieldNames      (U);
    Ubar            (0 0 10);  // 10 m/s in z-direction
    relaxation      0.3;
}

Diagnostic script — verify conservation across interface:

def check_interface_conservation(mesh, interface_name):
    """Verify mass/momentum conservation across interface."""
    mass_flux_in  = calculate_flux(mesh, interface_name, 'inlet')
    mass_flux_out = calculate_flux(mesh, interface_name, 'outlet')

    imbalance = abs(mass_flux_in - mass_flux_out) / mass_flux_in

    if imbalance > 0.001:  # 0.1% tolerance
        print(f"WARNING: {imbalance*100:.2f}% mass imbalance at {interface_name}")
        return False

    print(f"Interface {interface_name}: OK ({imbalance*100:.4f}% imbalance)")
    return True

Run this check before trusting any rotating machinery result.


Sin 7 — The 2D Assumption

When 2D simulations lie:

Geometry 3D Effect Missed Error Scale
Pipe bends Dean vortices 30–50% pressure drop error
Rotating machinery Tip vortices, secondary flows Efficiency off by 5–15%
Finite wings Spanwise flow, tip separation Lift/drag completely wrong near stall
Diffusers Corner separation Pressure recovery underestimated

The 2D vs. 3D Decision Matrix

Flow Characteristic 2D Acceptable? Reason
Infinite span, no end effects ✅ Yes Genuinely 2D geometry
Spanwise pressure gradients ❌ No Requires 3D momentum equations
Swirl or rotation present ❌ No Vorticity has 3D components
Curvature in the third dimension ⚠️ Risky May drive secondary flows
Symmetric geometry ⚠️ Check first Flow may be asymmetric even if geometry isn’t

Quantitative example — 90° pipe bend:

2D Simulation:  ΔP = 1.2 kPa   (no secondary flow captured)
3D Simulation:  ΔP = 1.8 kPa   (Dean vortices present)
Experimental:   ΔP = 1.9 kPa   (Dean vortices measured)

The 2D simulation is off by 37%. The 3D simulation is off by 5%.

Automated 3D requirement check:

import math

def should_run_3d(geometry, flow_conditions):
    """Heuristic: determine if 3D simulation is required."""

    if geometry.has_swirl or geometry.has_curvature_3d:
        return True

    if geometry.type == 'pipe_bend':
        Re = flow_conditions.Re
        curvature_ratio = geometry.bend_radius / geometry.pipe_diameter
        Dean = Re * math.sqrt(1.0 / curvature_ratio)
        if Dean > 10:  # Secondary flows become significant
            return True

    return False

When 2D is genuinely acceptable:
– Preliminary design exploration where you need trends, not values
– Infinite-span airfoil/cascade analysis (with validated 2D correlations)
– After you have explicitly validated that 3D effects are negligible for your specific case


3. The Diagnostic Toolkit

Is It the Mesh or the Solver?

When a simulation misbehaves, systematic diagnosis saves hours of blind troubleshooting. Follow this decision logic:

Simulation diverges or gives suspect results
│
├── Check residuals
│   ├── Continuity residual spikes at specific locations?
│   │   └── → Mesh quality problem (check skewness at those cells)
│   │
│   ├── Residuals oscillate without converging?
│   │   ├── Timestep too large → reduce Δt / CFL
│   │   └── Insufficient mesh resolution in high-gradient zones
│   │
│   └── Residuals converge but answer is wrong?
│       ├── Run mesh independence study
│       └── Check boundary conditions and turbulence model
│
├── Check mass balance
│   ├── Imbalance > 0.1%?
│   │   └── → Interface configuration error (see Sin 6)
│   └── Imbalance < 0.1%? → Not an interface problem
│
├── Check y+ distribution
│   ├── Values in forbidden zone (5–30)?
│   │   └── → Remesh inflation layers
│   └── Values correct? → Turbulence model may be the issue
│
└── Check cell quality in divergence region
    ├── High skewness at stagnation/separation? → Remesh
    └── Quality OK? → Likely a solver settings issue

Quick triage — three checks before anything else:
1. Extract skewness at the location of maximum residual. If > 0.85, it’s the mesh.
2. Check mass balance at inlets vs outlets. If > 0.1% error, it’s an interface.
3. Sample y⁺ on walls. If in the forbidden zone, it’s boundary layer setup.


Richardson Extrapolation

Richardson extrapolation estimates what your simulation would predict on an infinitely fine mesh — giving you a concrete measure of discretization error rather than a vague sense of confidence.

This is not an academic exercise. It’s the only rigorous way to know whether your fine mesh result is actually converged, or whether it’s just less wrong than the coarse mesh.

Procedure

Step 1 — Run three systematically refined meshes.
Create meshes with refinement ratio r ≈ 1.3 between levels.

Step 2 — Apply Richardson extrapolation:

import numpy as np

def richardson_extrapolation(f_coarse, f_medium, f_fine, r=1.3):
    """
    Estimate the infinite-mesh solution and quantify discretization error.

    Parameters:
        f_coarse, f_medium, f_fine : scalar outputs (e.g., drag coefficient)
        r : refinement ratio between mesh levels (typically 1.3)

    Returns:
        f_inf  : extrapolated 'true' value
        p      : apparent order of convergence
        error  : discretization error of the fine mesh (%)
    """
    # Apparent order of convergence
    p = np.log((f_coarse - f_medium) / (f_medium - f_fine)) / np.log(r)

    # Extrapolated value at infinite resolution
    f_inf = f_fine + (f_fine - f_medium) / (r**p - 1)

    # Discretization error of the fine mesh
    error = abs(f_inf - f_fine) / abs(f_inf) * 100

    return f_inf, p, error


# Example: drag coefficient mesh convergence study
Cd_coarse = 0.312
Cd_medium = 0.283
Cd_fine   = 0.245

Cd_inf, order, error = richardson_extrapolation(Cd_coarse, Cd_medium, Cd_fine)

print(f"Extrapolated Cd:        {Cd_inf:.4f}")
print(f"Apparent order:         {order:.2f}")
print(f"Discretization error:   {error:.1f}%")
Output:
Extrapolated Cd:        0.218
Apparent order:         1.85
Discretization error:   12.4%

Step 3 — Interpret the results:

Condition What It Means Action
Error < 5% Excellent mesh resolution Fine mesh is production-ready
Error 5–10% Acceptable for engineering work Document uncertainty; proceed with caution
Error > 10% Insufficient resolution Refine further before reporting
Apparent order ≈ 2 Expected for second-order schemes Scheme is behaving correctly
Apparent order < 1.5 Anomalous Check mesh quality and smoothness
Apparent order > 3 Also anomalous Check for lucky cancellation of errors

In the example above, the fine mesh has 12.4% discretization error — meaning the “true” drag coefficient is likely closer to 0.218, not the 0.245 the fine mesh predicts. This is a meaningful gap in an engineering context. The mesh needs further refinement for final validation.


4. Modern Workflow Strategies

Template-Based Meshing

Repeating the same meshing decisions from scratch on every project is one of the most common sources of inconsistency in CFD teams. Template-based meshing encodes your best-practice decisions once and applies them systematically.

External aerodynamics template (YAML):

# mesh_template_external_aero.yaml
template: external_aerodynamics
version: 2.1

parameters:
  chord_length:       1.0      # m
  y_plus_target:      1.0      # wall-resolved LES/DES
  reynolds_number:    1.0e6

domain:
  upstream:           8  * chord_length
  downstream:        12  * chord_length
  sides:              8  * chord_length

surface_mesh:
  divisions_per_length:   20
  trailing_edge_divisions: 40
  curvature_angle:         15   # degrees

inflation:
  layers:             18
  growth_ratio:       1.2
  total_thickness:    auto_calculated   # uses δ formula at runtime

refinement_zones:
  wake:
    length:           5  * chord_length
    width:            2  * thickness
  leading_edge:
    radius:           0.2 * chord_length
  trailing_edge:
    radius:           0.1 * chord_length

Benefits of template-based meshing:
– 40–60% reduction in meshing time for recurring problem types
– Consistent quality across projects and engineers
– Institutional knowledge capture — when the expert leaves, the template stays
– Auditable decisions — every mesh can be traced to a parameter set


Automated QA Pipeline

Manual mesh quality checking is tedious, error-prone, and inconsistent between engineers. Script it.

import meshio
import numpy as np

class MeshQA:
    """Automated mesh quality assurance pipeline."""

    def __init__(self, mesh_file):
        self.mesh    = meshio.read(mesh_file)
        self.results = {}

    def check_skewness(self, threshold=0.85):
        """Check that less than 0.5% of cells exceed the skewness threshold."""
        skewness    = self.calculate_skewness()
        failed      = np.sum(skewness > threshold)
        percent     = failed / len(skewness) * 100

        self.results['skewness'] = {
            'max':             np.max(skewness),
            'mean':            np.mean(skewness),
            'failed_cells':    int(failed),
            'failed_percent':  percent,
            'pass':            percent < 0.5
        }

    def check_y_plus(self, walls, target_range=(1, 5)):
        """Verify y⁺ on all specified wall patches."""
        y_plus_values = []
        for wall in walls:
            y_plus_values.extend(self.extract_y_plus(wall))

        y_plus_values    = np.array(y_plus_values)
        in_range         = np.sum(
            (y_plus_values >= target_range[0]) &
            (y_plus_values <= target_range[1])
        )
        percent_in_range = in_range / len(y_plus_values) * 100

        self.results['y_plus'] = {
            'min':              np.min(y_plus_values),
            'max':              np.max(y_plus_values),
            'mean':             np.mean(y_plus_values),
            'in_range_percent': percent_in_range,
            'pass':             percent_in_range > 90
        }

    def check_growth_rates(self, max_ratio=1.5):
        """Check for abrupt cell size changes between adjacent cells."""
        ratios          = self.calculate_size_ratios()
        bad_transitions = np.sum(ratios > max_ratio)

        self.results['growth_rates'] = {
            'max_ratio':        np.max(ratios),
            'bad_transitions':  int(bad_transitions),
            'pass':             bad_transitions == 0
        }

    def generate_report(self):
        """Print a comprehensive QA summary."""
        lines = ["=" * 60, "MESH QUALITY ASSURANCE REPORT", "=" * 60]
        all_passed = True

        for check, data in self.results.items():
            status = "✓ PASS" if data['pass'] else "✗ FAIL"
            if not data['pass']:
                all_passed = False
            lines.append(f"\n{check.upper()}: {status}")
            for key, value in data.items():
                if key != 'pass':
                    lines.append(f"  {key}: {value}")

        lines.append("\n" + "=" * 60)
        lines.append("OVERALL: " + ("✓ MESH APPROVED" if all_passed else "✗ MESH REQUIRES ATTENTION"))
        lines.append("=" * 60)
        return "\n".join(lines)


# Usage
qa = MeshQA("airfoil_mesh.vtk")
qa.check_skewness()
qa.check_y_plus(['wing_upper', 'wing_lower', 'fuselage'])
qa.check_growth_rates()
print(qa.generate_report())

Integrate this into your CI/CD pipeline if you run CFD in a production environment. A mesh that fails QA should not be allowed to consume solver hours.


Mesh Type Selection Guide

There is no universally best mesh type. Each has a domain where it excels:

Mesh Type Best Application Convergence Memory Setup Effort
Tetrahedral Complex geometry, rapid prototyping Moderate Medium Low
Hexahedral Simple/sweepable geometry, structured flows Excellent Low High
Polyhedral Industrial flows, complex internal passages Very Good Higher Medium
Cut-Cell (Cartesian) Rapid CAD-to-mesh, external aero screening Good Medium Very Low
Prism/Wedge layers Boundary layers (combined with above) Excellent in BL Low Medium

Practical hybrid strategy:
1. Use tetrahedral for complex geometry (automated, fast)
2. Add prism/inflation layers at all walls
3. Convert bulk tetrahedra to polyhedral in your solver (OpenFOAM: polyDualMesh; Fluent: built-in converter)
4. Result: polyhedral core + structured boundary layers — best of both

When hexahedral is worth the effort:
– Fully structured channel flows
– Pipe flows with known geometry
– Turbomachinery passages where you have parametric CAD control
– Any case where you need the highest possible numerical accuracy and have time to invest


5. The Complete Verification Workflow

This is the full process, from CAD import to production result. Treat each phase as mandatory, not optional.


Phase 1: Pre-Mesh Preparation

CAD cleanup — what to remove vs. what to keep:

Feature Action Reason
True duplicates, coincident surfaces Remove Causes mesher failures
Slivers and zero-area faces Remove Degenerate geometry
Fillets < 1 mm in low-velocity regions Safe to remove Minimal flow impact
Fillets < 1 mm aligned with flow Preserve May control separation
Sharp trailing edges Add 0.1 mm virtual fillet Prevents degenerate cells
Small holes not connected to fluid domain Remove (fill) Leaks in domain

Domain strategy checklist:
– [ ] Calculate minimum domain size using the formulas in Sin 5
– [ ] Identify symmetry planes — but verify flow is actually symmetric before applying
– [ ] Plan refinement zones based on expected physics (wake, shear layers, recirculation zones)
– [ ] Confirm inlet placement is far enough that specified BC doesn’t distort the flow of interest


Phase 2: Mesh Generation Protocol

def generate_mesh_with_qa(geometry, flow_conditions):
    """Best-practice mesh generation workflow."""

    # Step 1: Calculate key parameters
    y_plus = 1.0 if flow_conditions.reynolds < 5e5 else 30.0
    delta_y = calculate_first_cell_height(y_plus, flow_conditions)

    # Step 2: Create surface mesh
    mesh = create_surface_mesh(
        geometry,
        min_size       = 0.1 * delta_y,
        curvature_angle = 15            # degrees
    )

    # Step 3: Add inflation layers
    mesh.add_inflation_layers(
        first_height = delta_y,
        layers       = 18,
        growth_ratio = 1.2
    )

    # Step 4: Add volume refinement zones
    mesh.refine_region('wake',       cells_across=10)
    mesh.refine_region('separation', cells_across=8)
    mesh.refine_region('leading_edge', cells_across=6)

    # Step 5: Run automated QA
    qa_results = run_mesh_qa(mesh)

    if not qa_results['all_pass']:
        refine_problem_areas(mesh, qa_results)
        qa_results = run_mesh_qa(mesh)  # Re-check after fixing

    return mesh

Do not proceed to the solver if QA fails. A failed mesh that runs is worse than a failed mesh that doesn’t — because a failed mesh that runs produces confidently wrong results.


Phase 3: Mesh Independence Study

Never skip this. “My mesh looks fine” is not mesh independence. Here is the minimum viable study:

Step 1 — Create three mesh levels:

Level Cell Count How to Create
Coarse ~25% of production target Global size × 2
Medium ~50% of production target Global size × 1.4
Fine 100% of production target Baseline

Step 2 — Run identical simulations on all three.
Same boundary conditions, turbulence model, solver settings, and convergence criteria. Change only the mesh.

Step 3 — Analyze convergence:

def mesh_independence_study(results):
    """
    Check if key outputs are mesh-independent.

    results: list of dicts, each with keys: cell_count, drag, lift,
             pressure_drop, heat_flux (or whatever your QoI are)
    """
    outputs     = ['drag', 'lift', 'pressure_drop', 'heat_flux']
    all_passed  = True

    for output in outputs:
        values = [r[output] for r in results]  # Coarse, Medium, Fine

        diff_cm = abs(values[0] - values[1]) / abs(values[1])  # Coarse→Medium
        diff_mf = abs(values[1] - values[2]) / abs(values[2])  # Medium→Fine

        print(f"\n{output}:")
        print(f"  Coarse  → Medium: {diff_cm*100:.2f}%")
        print(f"  Medium  → Fine:   {diff_mf*100:.2f}%")

        if diff_mf > 0.02:  # 2% convergence threshold
            print(f"  ⚠ WARNING: {output} is NOT mesh independent!")
            all_passed = False
        else:
            print(f"  ✓ Converged")

    return all_passed

Expected output for a converged study:

drag:
  Coarse  → Medium: 9.15%
  Medium  → Fine:   1.23%   ✓ Converged

lift:
  Coarse  → Medium: 7.82%
  Medium  → Fine:   0.89%   ✓ Converged

Important: Check all quantities of interest. A mesh that is independent for drag may not be independent for peak pressure or local heat flux.


Phase 4: Production Run and Monitoring

During the simulation — monitor more than residuals:

# Monitoring points to set up before running
monitor_points = {
    'stagnation':    [x_stag, y_stag, z_stag],   # Pressure
    'wake_near':     [x_wake_near, 0, 0],         # Velocity
    'wake_far':      [x_wake_far, 0, 0],          # Velocity
    'separation':    [x_sep, y_sep, 0],           # Velocity, TKE
}

# Report these quantities every N iterations:
# - Mass flux at inlet and outlet (imbalance < 0.1%)
# - Drag and lift coefficients
# - Probe point values (convergence of local quantities)
# - y+ min/max on walls

Residuals alone do not confirm convergence. A simulation can have beautifully converged residuals while integrated quantities (drag, heat flux) are still drifting. Always monitor your actual quantities of interest.

Post-processing validation checklist:
– [ ] Compare integrated quantities to experimental data or correlations if available
– [ ] Confirm no negative turbulence quantities (negative k, ω, or ε → model breakdown)
– [ ] Verify realistic velocity profiles at key cross-sections
– [ ] Check boundary layer development matches expected physics
– [ ] Run ±5% boundary condition sensitivity check (perturb inlet velocity/pressure)
– [ ] Apply Richardson extrapolation to quantify remaining discretization error


6. Industry Quality Standards: The Full Reference Table

This is the master reference. Print it, pin it, add it to your team wiki.

Parameter Target Warning Critical Notes
Skewness < 0.80 0.80–0.85 > 0.90 Higher tolerance for tet-dominated meshes; lower for hex
Aspect Ratio (core) < 50 50–100 > 200 BL cells can legitimately reach 1000+
Orthogonal Quality > 0.25 0.15–0.25 < 0.10 Naturally lower in inflation layers
Growth Rate 1.10–1.20 1.20–1.30 > 1.50 Use 1.05 for laminar or transitional flows
y⁺ (RANS + wall functions) 30–300 5–30 < 5 or > 500 Forbidden zone: 5–30
y⁺ (LES / DES / low-Re RANS) 0.5–5 5–15 > 15 Must resolve viscous sublayer
Cells in boundary layer 15–20 10–15 < 10 More for transition modelling
Domain size (upstream) ≥ 8L 5–8L < 5L L = characteristic length
Domain size (downstream) ≥ 12L 8–12L < 8L
Interface size ratio ≤ 2:1 2:1–3:1 > 5:1 Coarse:fine at interface
Mass imbalance < 0.01% 0.01–0.1% > 0.1% Check inlet vs outlet
Mesh independence (QoI change) < 1% 1–2% > 2% Between medium and fine mesh

7. Computational Cost Optimization

The 70/30 Cell Budget Rule

Aim to place 70% of your cells in critical regions (boundary layers, separation zones, vortex cores, shear layers) and 30% in benign regions (far field, large-scale flow structures).

Most engineers do this backwards. Large cells in the wake and tiny cells everywhere else is not the goal. Targeted refinement is.

Memory Estimation

Before generating your production mesh, estimate memory requirements:

Total RAM (GB) ≈ [Cell Count × (30 + 5 × N_variables) × 8 bytes] / 10⁹

Example:
  10 million cells, 10 variables (U, V, W, p, k, ε, T, ρ, μ, scalar)
  = 10×10⁶ × (30 + 50) × 8 / 10⁹
  ≈ 6.4 GB RAM

Add 20–30% overhead for solver and post-processing. If you’re close to your hardware limit, reduce cell count or split across more cores before you hit memory errors mid-run.

Compute Time Scaling

For reference when planning mesh studies:

Compute time ∝ Cell count × Iterations_to_converge

Increasing cells by 2× typically:
  - Doubles compute time (cell count)
  - Adds 10–20% more iterations (sparser initial solution)
  - Net: ~2.2–2.4× wall clock time

Practical planning rule:
  If Fine mesh = 24 hours, budget 3–4 days for a full independence study.

Parallel Scaling

For large meshes, verify your parallel decomposition doesn’t create interface problems:

Cells per core: target 500,000–2,000,000 cells/core
Decomposition: balance cell count AND minimise inter-processor boundaries
Avoid: placing domain interfaces on processor boundaries

8. The Meshing Mindset

Meshing is not a preprocessing step. It is the discretization of physics. Every cell size decision is an answer to the question: “What flow scales will I explicitly resolve, and what will I model or ignore?”

The engineers who consistently produce accurate CFD results aren’t necessarily using better solvers or faster hardware. They’re making better meshing decisions — and they’re making them deliberately, not by feel.

The Three Golden Rules

Rule 1: Respect the physics scale.
Your mesh must capture the smallest flow feature that matters to your quantity of interest. Not the smallest feature in the geometry — the smallest dynamically significant feature in the flow.

Rule 2: Validate, then trust.
Mesh independence study isn’t optional paperwork. Richardson extrapolation isn’t an academic exercise. They are the only rigorous way to know whether you have a result or a numerically expensive guess.

Rule 3: Automate the routine.
Manual quality checks are slow and inconsistent. Script your QA. Template your workflows. Spend your engineering judgment on physics decisions — not on checking whether skewness is above 0.85 in cell 847,293.

The Compounding Return on Good Meshing

An automotive OEM that implemented systematic meshing practices — mesh templates, automated QA, mandatory independence studies — reduced their CFD cycle time from 12 weeks to 6 weeks while improving accuracy from ±15% to ±5%. They didn’t buy faster hardware. They stopped making meshing mistakes.

The 40% time investment in meshing and verification pays back at 200%. Every time.


Quick Reference Card

PRE-MESH
├── Calculate y⁺ and Δy before opening the mesher
├── Set domain size: ≥ 8L upstream, ≥ 12L downstream
├── Preserve small features if they're in the flow path
└── Plan refinement zones based on expected flow physics

MESH GENERATION
├── Surface: curvature angle ≤ 15°, min size = 0.1 × Δy
├── Inflation: 18–20 layers, growth ratio 1.1–1.2
├── Volume: 70% of cells in critical regions
└── Run automated QA before any solve

QUALITY THRESHOLDS
├── Skewness:          < 0.85
├── Aspect ratio:      < 100 (core), < 1000 (BL)
├── Orthogonal quality: > 0.15
├── Growth rate:       < 1.3
└── y⁺ forbidden zone: NEVER 5–30

VERIFICATION
├── Run 3 mesh levels (coarse / medium / fine)
├── Key outputs must change < 2% between medium and fine
├── Apply Richardson extrapolation for final error quantification
└── Monitor mass balance (< 0.1% imbalance)

Based on research and industrial case studies compiled for turbulentthoughts.com. For corrections, additions, or case studies to include, the methodology described here applies regardless of solver: OpenFOAM, ANSYS Fluent, STAR-CCM+, or any other.

Leave a Reply

Your email address will not be published. Required fields are marked *