Skip to main content
Work in Progress

This page is currently under development. Content may be incomplete or contain inaccuracies. If you notice any errors or have suggestions, please contact us.

Use Case 1: Computational Fluid Dynamics (CFD) Simulation

CFD simulations solve the Navier-Stokes equations numerically to predict fluid flow, heat transfer, and related phenomena. These simulations generate large, time-dependent datasets that require careful data management for reproducibility, post-processing, and archival.

Workflow Overview

Geometry → Meshing → Solver setup → Simulation → Post-processing
CAD (STEP) → Mesh (MSH/polyMesh) → Case setup → Time-step results → Visualization & analysis

Key Concepts

Mesh Generation

The computational domain must be discretized into cells (finite volumes) or elements:

  • Structured mesh — Regular, hexahedral cells; efficient but limited to simple geometries
  • Unstructured mesh — Tetrahedral/polyhedral cells; flexible for complex geometries
  • Hybrid mesh — Combination of structured boundary layers and unstructured core
  • Adaptive mesh refinement (AMR) — Dynamically refines the mesh during simulation in regions of interest

Turbulence Modeling

Most engineering flows are turbulent. Modeling approaches (in order of increasing cost):

  • RANS (Reynolds-Averaged Navier-Stokes) — Time-averaged; cheapest; most common in industry
  • LES (Large Eddy Simulation) — Resolves large scales, models small scales; 10-100x more expensive than RANS
  • DNS (Direct Numerical Simulation) — Resolves all scales; extremely expensive; research only

Data Volume Challenges

A typical transient CFD simulation produces:

  • Mesh: 1-100 million cells
  • Fields per time step: velocity (3D), pressure, temperature, turbulence quantities
  • Time steps: thousands to millions
  • Total size: tens of GB to several TB per simulation

Solvers

  • OpenFOAM — Open-source, widely used in academia
  • ANSYS Fluent / CFX — Commercial, industry standard
  • SU2 — Open-source, optimization-focused
  • COMSOL Multiphysics — Multiphysics, user-friendly GUI

Meshing

  • Gmsh — Open-source mesh generator
  • snappyHexMesh (OpenFOAM) — Automatic mesh generation from STL surfaces
  • ANSYS Meshing / ICEM CFD — Commercial mesh tools

Post-Processing

  • ParaView — Open-source visualization for large datasets
  • Tecplot — Commercial visualization
  • Python (PyVista, matplotlib) — Scripted post-processing

Code Example: OpenFOAM Workflow

Click to expand OpenFOAM case setup and run
#!/bin/bash
# OpenFOAM CFD simulation workflow: flow over a backward-facing step

# Step 1: Create case directory structure
mkdir -p backwardStep/{0,constant,system}

# Step 2: Generate mesh
cd backwardStep
blockMesh # Generate structured mesh from blockMeshDict

# Step 3: Check mesh quality
checkMesh # Reports mesh statistics, non-orthogonality, skewness

# Step 4: Decompose for parallel run
decomposePar # Split domain across processors

# Step 5: Run solver in parallel
mpirun -np 8 simpleFoam -parallel > log.simpleFoam 2>&1

# Step 6: Reconstruct parallel results
reconstructPar

# Step 7: Post-process
# Calculate wall shear stress
simpleFoam -postProcess -func wallShearStress -latestTime

# Sample data along a line for comparison with experiments
postProcess -func "sampleDict" -latestTime

# Step 8: Convert to VTK for ParaView visualization
foamToVTK

echo "Simulation complete! Open backwardStep/VTK/ in ParaView"

Code Example: Post-Processing with Python

Click to expand Python post-processing script
#!/usr/bin/env python3
"""Post-process CFD results: extract velocity profiles and plot convergence."""

import numpy as np
import matplotlib.pyplot as plt
from pathlib import Path

# Step 1: Read OpenFOAM log file for residual convergence
def parse_residuals(log_file: Path) -> dict:
"""Parse OpenFOAM log file for residual history."""
residuals = {"Ux": [], "Uy": [], "p": [], "iteration": []}
iteration = 0

with open(log_file, "r") as f:
for line in f:
if "Solving for Ux" in line:
res = float(line.split("Final residual = ")[1].split(",")[0])
residuals["Ux"].append(res)
iteration += 1
residuals["iteration"].append(iteration)
elif "Solving for Uy" in line:
res = float(line.split("Final residual = ")[1].split(",")[0])
residuals["Uy"].append(res)
elif "Solving for p" in line:
res = float(line.split("Final residual = ")[1].split(",")[0])
residuals["p"].append(res)

return residuals

residuals = parse_residuals(Path("log.simpleFoam"))

# Step 2: Plot convergence
fig, ax = plt.subplots(figsize=(10, 5))
for field in ["Ux", "Uy", "p"]:
ax.semilogy(residuals["iteration"][:len(residuals[field])],
residuals[field], label=field)
ax.set_xlabel("Iteration")
ax.set_ylabel("Residual")
ax.set_title("Solver Convergence")
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig("convergence.pdf", dpi=300)
plt.savefig("convergence.png", dpi=300)

# Step 3: Read sampled velocity profile
data = np.loadtxt("postProcessing/sampleDict/0/line_U.csv",
delimiter=",", skiprows=1)
y = data[:, 0]
Ux = data[:, 1]

fig, ax = plt.subplots(figsize=(6, 8))
ax.plot(Ux, y, "b-", linewidth=2)
ax.set_xlabel("Velocity Ux (m/s)")
ax.set_ylabel("y (m)")
ax.set_title("Velocity Profile")
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig("velocity_profile.pdf", dpi=300)
plt.savefig("velocity_profile.png", dpi=300)

print("Post-processing complete!")

Expected Outputs

  • Field data — Velocity, pressure, temperature at each cell for each saved time step
  • Residual history — Convergence log showing how solver residuals decrease
  • Derived quantities — Wall shear stress, drag/lift coefficients, heat flux
  • Visualizations — Contour plots, streamlines, velocity profiles (PDF + PNG)
  • Validation plots — Comparison of simulation results with experimental data

Computational Requirements

Simulation TypeMesh SizeCPU CoresRAMStorageTime
2D steady RANS50K cells42-4 GB<1 GBMinutes
3D steady RANS5M cells32-6416-64 GB5-20 GBHours
3D transient LES20M cells128-51264-256 GB100 GB - 1 TBDays-weeks
DNS (research)1B+ cells1000+1+ TB10+ TBWeeks-months

Common Issues & Troubleshooting

Common Problems

Solution divergence

  • Reduce under-relaxation factors or CFL number
  • Improve mesh quality (check non-orthogonality, skewness)
  • Start with first-order schemes, then switch to second-order after partial convergence

Mesh quality issues

  • Non-orthogonality above 70° causes instability — refine problematic regions
  • Ensure adequate boundary layer resolution (y+ appropriate for turbulence model)
  • Use checkMesh (OpenFOAM) to identify problems before running

Storage overflow

  • Save only every Nth time step for transient simulations
  • Write only fields of interest (not all default fields)
  • Use binary output format instead of ASCII
  • Compress old time steps and archive to long-term storage

Key Considerations

  • Version-control your case setup — Input files (dictionaries, boundary conditions) should be in Git
  • Document mesh convergence — Run at least 3 mesh resolutions to demonstrate grid independence
  • Save restart files — For long simulations, save checkpoint files at regular intervals
  • Validate against experiments — Simulation results without validation have limited scientific value
  • Plan storage early — Estimate total output size before starting and ensure sufficient storage