Overview
Like every new aerodynamics enthusiast - and, as I have come to learn, like every person that had just started as the Aero S&C Lead at ADT - I occasionally dreamt of designing my own airfoil. There is precedence to do so for cases where it makes sense. One legendary member of the AeroDesign Team, for instance, is often remembered by his flying wing airfoil designs which won several competitions.
AirfoilOptimizer was my short-lived journey into airfoil design. My idea was not to simply alter the curve of an airfoil manually, but rather arrive at a genetically optimized shape and then tailor it to the needs of that years’ competition.
Parametrization
The first step of optimizing an aerodynamic shape numerically/algorithmically is an accurate way to capture the geometry by setting (and then varying) numerical parameters. I experimented with two approaches: PARSEC and Bezier splines.
PARSEC parametrizes an airfoil curve with 11 distinct parameters that have geometric significance to the airfoil. Mathematically, it is two polynomials (one for the upper, one for the lower surface) where the coefficients for each surface are found by solving a system of linear equations.
I also played around with Bezier curves. I used cubic curves to interpolate between 10 control points at each surface, and allowed a solver to vary the y-positions of the control points while keeping the x-positions fixed at locations that this author judged important to the airfoil shape (a few grouped at the leading edge, others at quarter and half chord, and a few more at the trailing edge.)
Objective
The other requirement for an optimization problem is an objective to minimize. In the case of airfoils, that is usually some aerodynamic coefficient (drag coefficient), or ratios of coefficients (lift-to-drag ratio), or perhaps more tailored expressions to capture off-design performance.
In order to get these coefficients, I wrote functionality to run XFOIL from Python after failing to compile DARCorp’s FORTRAN-based tools. The core of the functionality is simple:
def runXFOIL(routine, xfoilPath='./xfoil'):
process = subprocess.Popen(xfoilPath, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
stdout, stderr = process.communicate(routine, timeout=15)
if os.path.exists('polar.dat'):
polar = parsePolar('polar.dat')
os.remove('polar.dat')
else:
print(f"Error running XFOIL: {stderr}")
polar = None
if os.path.exists('polar.dump'):
os.remove('polar.dump')
return polar
But I also implemented supporting methods and classes to parse a .dat file, store the results, and allow the user to run single point operations (single CL, single alpha) or sweep operations.
Optimization
For optimization, I implemented a genetic algorithm using PyGad, a Python library with built-in algorithms for every GA-related operation (setting up the genes, initializing the generations, cross-overs, mutations, etc.) Some results are shown below - the first with a PARSEC airfoil, the second with a Bezier one, both with a simple objective: maximize lift-to-drag ratio.
Reflections
The results are interesting to look at, but I unfortunately did not take this project much further than these GIFs. For the rest of the year, I stuck with selecting airfoils instead of designing them. The argument for this is fair - without access to wind tunnel facilities, the user is left to rely on XFOIL results. There is a strong point to be made about the reliability of battle-proven airfoils versus the perils of designing your own…
However, I had a blast while playing around with this, and is something that I am quite interested in revisiting, especially with my latest endeavor, the Human-Powered Research Team (HPFRT).