Original author: Munehiro Fukuda
Revisions 2019: Morris Bernstein
A simple polygon is defined to be convex if the line segment between any pair of points inside the polygon is fully contained by the polygon. Informally, the polygon is convex if it matches the “rubber band” placed around its vertex points. The convex hull of a set of points (in the plane) is the smallest convex polygon that contains all the points.
Finding the convex hull is a seminal problem in the field of computational geometry There are numerous applications, including computer graphics and image analysis, robot motion planning (path finding), game theory, epidemiology, compilers, etc.
The
Graham scan algorithm
solves the problem in two steps by first sorting the points with respect to
the angle each point makes with lowest point. Then it does a
linear-time scan of the sorted points to determine whether each
point makes a right or left turn with respect to the next point.
The cost of sorting,
O(n log n)
1,
dominates the computational complexity of the algorithm.
Another algorithm uses a divide-and-conquer approach. It takes
advantage of the fact that a convex hull is represented by an
ordered list of points to merge a pair of hulls in linear time.
By repeatedly dividing the points in half to get smaller subsets, we
can get a quicksort-like
O(n log n)
algorithm.
The merge operation works by finding the polygon with the lowest point and dividing the other polygon into upper and lower chains. The points of the lower chain cannot be in the merged hull and can be immediately discarded. The upper chain is necessarily sorted with respect to the lowest point of the first polygon, so its points can be merged with the first polygon in linear time. A linear-time Graham scan then finds the hull of the merged set of points.
You have been given a C++ program that benchmarks the Graham scan and divide-and-conquer algorithms. Your task is to modify the divide-and-conquer implementation to take advantage of the multi-core architecture of modern machines.
Your strategy will be to fork your process and let the parent and child processes each process half the set. The child should then pipe its results back to the parent process. The parent will then read the results from the child and perform the merge.
The parent process should wait for the child to complete so that the parent can check the exit status of the child process. If there are any failures, the program should exit with a non-zero exit status (indicating failure). If the program fails for any reason, fix the problem (if it isn't transient) and rerun the experiment.
To make it interesting, your test data should contain 10 million points (but do not include the test data in the hand-in materials).
Determine the performance of the Graham scan algorithm versus
concurrent d&c for 1, 2, 4, 8, and 16 "processors".
Use the
README
file to report your results.
While interesting, you don't really need to understand the
convex hull algorithm to complete the assignment.
Your instructor's
solution is only 55 lines, implemented in a separate
#include
file2.
You don't need to modify any of the provided source. Just create
the required
#include
files and compile with the right flags.
You can use the known-good Graham and non-concurrent d&c solutions to cross-check the correctness of your implementation. A script to automate your testing is desirable. Otherwise, how are you going to convince your instructor that your solution is correct?
Report performance for your program compiled with optimization
levels
-O0
and
-O3
.
Normally, since we are concerned with performance, we would compile
with maximum optimizations only, but this is an opportunity to see
how much code improvement the optimizer produces.
Use appropriate preprocessor definitions using the
-D
flag on the compile command.
Don't forget to include your
BUILD
and
RUN
scripts.
The tarball includes a pair of Python utility scripts.
generate_data.py
will generate random input data.
plot.py
can be used to generate
svg
image files that illustrate the data.
n
is the number of points in the input set. There exist output sensitive algorithms that perform in O(n log h)
where h
is the number of points in the hull. Generally, h << n
..inc
instead of .h
because it's not really a header file. .h
and .inc
are just conventions.