OpenFOAM
You can find this application in the demos
folder of your Jupyter notebook environment.
- openfoam.ipynb
- Allrun
- Allclean
- license
- U
- p
- blockMeshDict
- controlDict
- decomposeParDict
- fvSchemes
- fvSolution
OpenFOAM Potential Flow Simulation
This tutorial demonstrates how to run an OpenFOAM (Open-source Field Operation And Manipulation) potential flow simulation using Camber. Specifically, we’ll use the potentialFoam
solver to simulate potential flow around a cylinder, which is a classic benchmark problem in computational fluid dynamics.
Potential flow theory assumes the fluid is:
- Incompressible: Constant density throughout the flow
- Irrotational: No vorticity in the flow field
- Inviscid: No viscous effects (no boundary layers)
- Steady: Time-independent flow field
This example demonstrates fundamental OpenFOAM features including:
- Non-orthogonal mesh generation using blockMesh
- Analytical solution comparison for validation
- Dynamic code generation for complex geometries
- Post-processing and visualization of potential flow results
The complete OpenFOAM case includes:
- Case management scripts:
Allrun
andAllclean
for automated workflow execution - Initial conditions:
0.orig/
directory with velocity (U
) and pressure (p
) boundary conditions - System configuration:
system/
directory containing mesh generation (blockMeshDict
), simulation control (controlDict
), numerical schemes (fvSchemes
,fvSolution
), and parallel decomposition (decomposeParDict
) settings - License file: Standard OpenFOAM case licensing information
Unlike viscous flow, potential flow produces smooth streamlines with no flow separation or boundary layers, making it ideal for validation and providing initial conditions for more complex simulations.
import camber
Configure and Submit OpenFOAM Job
Here we configure and submit a potentialFoam
simulation job for potential flow around a cylinder:
command="bash ./Allrun"
: Runs the complete workflowengine_size="XSMALL"
: Indicates the engine size for the simulation
The simulation workflow includes:
- Mesh generation: Creates a 10-block structured mesh around the cylinder using
blockMesh
- Potential flow solution: Solves Laplace’s equation (∇²φ = 0) for the velocity potential
- Analytical comparison: Generates analytical solution for validation using coded function objects
- Post-processing: Creates streamlines and exports results to VTK format
# Create and submit potentialFoam simulation job
openfoam_job = camber.openfoam.create_job(
command="bash ./Allrun", # Execute complete potentialFoam workflow
engine_size="XSMALL" # Small engine sufficient for this demo
)
Check Job Status
Monitor the job status to track the simulation progress:
openfoam_job.status
Monitor Job Execution
To monitor job execution in real-time, use the read_logs()
method to view the OpenFOAM solver output and simulation progress (please, stand by patiently until the output appears):
# Read real-time logs from the potentialFoam simulation
# This shows mesh generation, pressure equation convergence, and analytical comparison
openfoam_job.read_logs()
Install Visualization Dependencies
Install PyVista for advanced 3D visualization and VTK file processing:
# Install PyVista for VTK file processing and 3D visualization
%pip install -qq pyvista
Analyze and Visualize Potential Flow Results
Once the potentialFoam
simulation is complete, we can analyze and visualize the potential flow results. This section:
- Loads VTK output files: Reads the OpenFOAM results exported in VTK format for post-processing
- Processes mesh and boundary data: Extracts the computational mesh and cylinder boundary geometry
- Handles velocity field data: Retrieves velocity vectors from the potential flow solution
- Creates cylinder mask: Uses computational geometry to exclude interior points of the cylinder
- Interpolates to regular grid: Converts unstructured CFD data to a regular grid for visualization
- Generates streamline plot: Creates visualization showing smooth potential flow streamlines
The potential flow visualization demonstrates key characteristics:
- Smooth streamlines: No flow separation or vorticity due to inviscid assumption
- Symmetrical flow: Perfect fore-aft symmetry around the cylinder
- No boundary layers: Fluid can slip along the cylinder surface (no viscosity)
- Conservation of mass: Streamlines compress around the cylinder, increasing velocity
- Analytical validation: Results can be compared directly with theoretical solution
Note: This idealized flow differs significantly from real viscous flow, which would show boundary layers, flow separation, and wake formation behind the cylinder.
# Import required libraries for data processing and visualization
import numpy as np
import matplotlib.pyplot as plt
import pyvista as pv
from scipy.interpolate import griddata
from matplotlib.path import Path
from scipy.spatial import ConvexHull
# ============================================================================
# LOAD AND PROCESS VTK DATA FROM POTENTIALFOAM SIMULATION
# ============================================================================
# Load the potential flow solution and cylinder boundary
# AFTER `from matplotlib.path import Path` …
from pathlib import Path as FS # avoid the name clash with matplotlib.Path
run = next(d for d in FS('VTK').iterdir() if d.is_dir()) # first sub-folder
mesh = pv.read(run / 'internal.vtu')
cylinder = pv.read(run / 'boundary' / 'cylinder.vtp')
# Extract cell centers and velocity data from the potential flow solution
cells = mesh.cell_centers()
centres = cells.points[:, :2] # 2D cell-center coordinates (N×2)
U = mesh.cell_data["U"][:, :2] # Velocity components from potential flow [u, v] (N×2)
# ============================================================================
# CREATE CYLINDER BOUNDARY POLYGON FOR MASKING
# ============================================================================
# Extract 2D cylinder boundary points and create convex hull
c2d = cylinder.points[:, :2]
hull = ConvexHull(c2d)
poly = c2d[hull.vertices] # Ordered boundary vertices
poly_path = Path(poly) # Create matplotlib Path for inside/outside tests
# Remove cell centers that are inside the cylinder body
keep = ~poly_path.contains_points(centres)
centres, U = centres[keep], U[keep]
# ============================================================================
# INTERPOLATE TO REGULAR GRID FOR STREAMLINE VISUALIZATION
# ============================================================================
# Create regular rectangular grid for streamline plotting
nx = ny = 300 # Grid resolution
xi = np.linspace(centres[:,0].min(), centres[:,0].max(), nx)
yi = np.linspace(centres[:,1].min(), centres[:,1].max(), ny)
X, Y = np.meshgrid(xi, yi)
# Interpolate velocity components from unstructured to structured grid
u = griddata(centres, U[:,0], (X, Y), method='linear') # x-velocity component
v = griddata(centres, U[:,1], (X, Y), method='linear') # y-velocity component
# Apply cylinder mask to grid (remove points inside cylinder)
inside = poly_path.contains_points(np.c_[X.ravel(), Y.ravel()]).reshape(X.shape)
u[inside] = v[inside] = np.nan # Set interior points to NaN
speed = np.hypot(u, v) # Calculate velocity magnitude
# ============================================================================
# CREATE POTENTIAL FLOW STREAMLINE VISUALIZATION
# ============================================================================
# Create streamline plot showing smooth potential flow patterns
plt.figure(figsize=(6, 6))
plt.streamplot(X, Y, u, v,
color=speed, # Color streamlines by velocity magnitude
cmap='viridis', # Use viridis colormap
density=1.5, # Streamline density
linewidth=1) # Line thickness
# Draw cylinder outline
poly_closed = np.vstack([poly, poly[0]]) # Close the polygon
plt.plot(poly_closed[:,0], poly_closed[:,1], 'k', lw=1.5)
# Format plot for clean presentation
plt.axis('equal') # Equal aspect ratio to preserve geometry
plt.axis('off') # Hide axes for cleaner scientific visualization
plt.tight_layout() # Optimize layout
plt.title('Potential Flow Around Cylinder\n(Smooth streamlines, no separation)',
fontsize=12, pad=20)
plt.show()