This is a Python 2 script to take in a file in one of two formats (unknown), and standardize it.
The data are coordinates in the xy-plane. They represent airfoils in the first and fourth quadrants: x should always be positive, and y may be positive or negative. The magnitude of each coordinate of any given point should be between 0
and 1
, inclusive.
The file may or may not have header data (e.g., RAE 103 AIRFOIL
), and it may or may not list the total number of points at the top or bottom of the file. We want to ignore all of that, if it's present.
Ideally, we want the x-coordinates to go from 0
to 1
and then back to 0
. However, in some input formats, it goes from 0
to 1
, then jumps back and goes from 0
to 1
again. The goal of this parser is to identify those files and reverse the second half, and delete the repeated points.
This code works, and I've tried to make it Pythonic. There are a couple places in particular where I am considering improvements, but any suggestions are welcome.
import math
import fileinput # to read files from stdin
"""
Syntax to run:
python AirfoilRead.py < infile.dat > outfile.geo
Some assumptions by this program:
* all valid values are in [0, 1]
* File does not have strange formating (from UIUC airfoil data base rae103 and ag13 will be called standard file types)
* all points relatively sequential (no Delaunay triangulation needed)
* only one discontinuity allowed in data, after which data reverses
* distances of > 0.5 between two points are discontinuities
"""
def parse_point(string):
"""
Takes as input a string of the form "0.123 4.567" and creates the
corresponding tuple of the form (0.123, 4.567).
If the string-to-float conversion fails (e.g., on "0.123 a.bcd"), or there
are not exactly two components, this function returns None.
"""
parts = string.split() # split by whitespace
if len(parts) is not 2:
return None
try:
parts = [float(component) for component in parts]
except ValueError: # conversion failed on one or more parts
return None
# Make sure all components are between 0 and 1.
if all(0 <= component <= 1 for component in point):
return tuple(parts)
"""
Read in all input, convert to points, and create the list.
"""
points = []
for line in fileinput.input():
# Convert the line to a tuple.
point = parse_line_to_point(line)
# Check for validity: valid parse, valid range.
if point is not None:
# Make sure all components are between 0 and 1.
# or: if 0 <= min(point) and max(point) <= 1:
if all(0 <= component <= 1 for component in point):
points.append(point)
"""
Iterate over the list checking for discontinuities (distance > 0.5).
If we find one, split the list, reverse the second half, and concatenate.
Limit to one discontinuity (break out at the first).
"""
for i in xrange(len(points) - 1):
p0, p1 = points[i], points[i+1]
distance = math.hypot(p1[0] - p0[0], p1[1] - p0[1])
if distance > 0.5:
# Discontinuity! Split here.
first_half = points[:i+1] # inclusive upper bound
second_half = points[i+1:-1][::-1] # exclude last, then reverse
points = first_half + second_half
break
"""
Check for duplicate data at the end.
Trim the last element if both elements are duplicated (point is identical).
"""
if points[0] == points[-1]:
points = points[:-1]
"""
Print the points to stdout.
"""
for point in points:
print "(%f, %f)" % point
print ("TotalPoints = %i;" % len(points))
My thoughts:
- The
parse_point
method is pretty good. I would like the first
for
loop, which reads the points from the file intopoints
, to be improved. I was considering a comprehension along the lines ofpoints = [parse_point(line) for line in fileinput.input()] points = [point for point in points if point is not None and all(0 <= component <= 1 for component in point)]
but I think this may violate PEP 20.3–4. Thoughts?
- I'm not sure about the double slice on
second_half
. I could do it in a single slice with something like[-1:i:-1]
, but then you have to worry about inclusive/exclusive, and it's less clear (to me) what it's doing. What's better? (How about performance?) What about.reverse()
? - Is there a better way to do pairwise iteration with index? I don't really want to do
enumerate(zip(points, points[1:]))
, because it just seems clumsy. But is this the way to go? Or are the dreaded index traversals the best solution here?
Please note also that I'd like to avoid anything too advanced (such as a lambda
filter for reading in the points) because I'm working on this with someone who's learning Python for the first time.
General thoughts, comments, suggestions?