Note
Go to the end to download the full example code.
Lid-Driven Cavity Flow - Finite Volume SIMPLE#
Finite volume solver using the SIMPLE algorithm for pressure-velocity coupling on a collocated grid with Rhie-Chow interpolation.
Usage:
uv run python compute_LDC.py --N 32 --Re 100
uv run python compute_LDC.py --N 64 --Re 400 --tol 1e-6
Setup and Configuration#
Parse command line arguments and setup directories.
import argparse
import os
from ldc import FVSolver
from utils import get_project_root, LDCPlotter, GhiaValidator, plot_validation
parser = argparse.ArgumentParser(description="FV-SIMPLE solver for lid-driven cavity")
parser.add_argument(
"--N", type=int, default=32, help="Grid cells in each direction (default: 32)"
)
parser.add_argument(
"--Re", type=int, default=100, help="Reynolds number (default: 100)"
)
parser.add_argument(
"--tol", type=float, default=1e-7, help="Convergence tolerance (default: 1e-7)"
)
parser.add_argument(
"--max-iter", type=int, default=50000, help="Max iterations (default: 50000)"
)
args = parser.parse_args()
N = args.N
Re_number = args.Re
project_root = get_project_root()
data_dir = project_root / "data" / "FV-Solver"
fig_dir = project_root / "figures" / "FV-Solver"
data_dir.mkdir(parents=True, exist_ok=True)
fig_dir.mkdir(parents=True, exist_ok=True)
Initialize Solver#
Create the finite volume solver with SIMPLE algorithm.
solver = FVSolver(
Re=Re_number,
nx=N,
ny=N,
alpha_uv=0.6,
alpha_p=0.3,
convection_scheme="TVD",
limiter="MUSCL",
linear_solver_tol=1e-8,
)
print(
f"Solver configured: Re={solver.params.Re}, Grid={solver.params.nx}x{solver.params.ny}"
)
print(
f" Convection scheme: {solver.params.convection_scheme} with {solver.params.limiter} limiter"
)
print(
f" Linear solver: PETSc (BiCGSTAB + GAMG), tol={solver.params.linear_solver_tol:.0e}"
)
MLflow Tracking#
Setup MLflow experiment tracking with nested runs.
Re_str = f"Re{Re_number}"
is_hpc = "LSB_JOBID" in os.environ
experiment_name = "HPC-FV-Solver" if is_hpc else "FV-Solver"
solver.mlflow_start(experiment_name, f"N{N}_{Re_str}", parent_run_name=Re_str)
Solve#
Run the solver until convergence or max iterations.
solver.solve(tolerance=args.tol, max_iter=args.max_iter)
Save Results#
Save solution to HDF5 and log as MLflow artifact.
output_file = data_dir / f"LDC_N{N}_{Re_str}.h5"
solver.save(output_file)
solver.mlflow_log_artifact(str(output_file))
print(f"\nResults saved to: {output_file}")
Validation Plots#
Generate plots and log to MLflow.
plotter = LDCPlotter(output_file)
validator = GhiaValidator(output_file, Re=Re_number, method_label="FV-SIMPLE")
fig_path = fig_dir / f"ghia_validation_N{N}_{Re_str}.pdf"
plot_validation(validator, output_path=fig_path)
solver.mlflow_log_artifact(str(fig_path))
print(" ✓ Ghia validation saved")
fig_path = fig_dir / f"convergence_N{N}_{Re_str}.pdf"
plotter.plot_convergence(output_path=fig_path)
solver.mlflow_log_artifact(str(fig_path))
print(" ✓ Convergence saved")
fig_path = fig_dir / f"fields_N{N}_{Re_str}.pdf"
plotter.plot_fields(output_path=fig_path)
solver.mlflow_log_artifact(str(fig_path))
print(" ✓ Fields saved")
fig_path = fig_dir / f"streamlines_N{N}_{Re_str}.pdf"
plotter.plot_streamlines(output_path=fig_path)
solver.mlflow_log_artifact(str(fig_path))
print(" ✓ Streamlines saved")
Summary#
End MLflow run and print summary.
solver.mlflow_end()
print("\nSolution Status:")
print(f" Converged: {solver.metrics.converged}")
print(f" Iterations: {solver.metrics.iterations}")
print(f" Final residual: {solver.metrics.final_residual:.6e}")
print(f" Wall time: {solver.metrics.wall_time_seconds:.2f} seconds")