Profiling.

This commit is contained in:
Casper 2018-10-18 18:41:18 +02:00
parent 54c97fc832
commit 776412995c
No known key found for this signature in database
GPG Key ID: B1156723DB3BDDA8
7 changed files with 204 additions and 96 deletions

View File

@ -1,9 +1,11 @@
# Use atan2 instead of acos to calc angle; atan2(x,y) of the point we potentially want to add
from math import acos, sqrt
from profile import Profiler
from util import Vector, Point, gen_point
@Profiler("calculating angle")
def calc_angle(v1: Vector, v2: Vector) -> float:
dot = (v1.x * v2.x) + (v1.y * v2.y)
len_1 = sqrt(v1.x**2 + v1.y**2)
@ -13,22 +15,25 @@ def calc_angle(v1: Vector, v2: Vector) -> float:
return acos(max(min(tmp, 1), -1)) # acos is only defined in [-1,1]
@Profiler("calculating vector")
def calc_vector(p1: Point, p2: Point) -> Vector:
return Vector((p2.x - p1.x), (p2.y - p1.y))
return Vector((p2.x - p1.x), (p2. y - p1.y))
@Profiler("gift_wrapper")
def rapper(points: set):
min_pt = min(points)
hull = [min_pt]
comp_vec = Vector(0, 1)
while True:
hull.append(min(points - {hull[-1]},
key=lambda p: calc_angle(comp_vec,
calc_vector(hull[-1], p))))
comp_vec = calc_vector(hull[-2], hull[-1])
with Profiler("iterating points", excluded=("calculating angle", "calculating vector")):
while True:
hull.append(min(points - {hull[-1]},
key=lambda p: calc_angle(comp_vec, calc_vector(hull[-1], p))))
if hull[-1] == min_pt:
return hull
comp_vec = calc_vector(hull[-2], hull[-1])
if hull[-1] == min_pt:
return hull
if __name__ == '__main__':

View File

@ -1,8 +1,10 @@
from util import gen_point, Side, Point, display
from profile import Profiler
from util import gen_point, Side, display
@Profiler("calculating sidedness")
def sidedness(p1, p2, p3, eps=0.0000001):
# Find line from p1 to p2, ask where p3 is in relation to this
y = p3.y * (p2.x - p1.x)
@ -18,37 +20,30 @@ def sidedness(p1, p2, p3, eps=0.0000001):
return Side.BELOW
# test
p1 = Point(4, 4)
p2 = Point(0, 0)
p3 = Point(5, 2)
# print(sidedness(p1, p2, p3))
@Profiler("graham_scan")
def graham_scan(points):
# A funky issue where both a and b become negative in the sidedness test causes us to have to use
# Side.ABOVE for both tests, regardless of UH or LH.
sorted_points = sorted(points)
with Profiler("sorting points"):
sorted_points = sorted(points)
UH = sorted_points[:2]
with Profiler("iterating upper hull", excluded=("calculating sidedness",)):
for s in sorted_points[2:]:
while len(UH) > 1 and (sidedness(UH[-2], UH[-1], s) != Side.ABOVE):
del UH[-1]
UH.append(s)
for s in sorted_points[2:]:
while len(UH) > 1 and (sidedness(UH[-2], UH[-1], s) != Side.ABOVE):
del UH[-1]
UH.append(s)
reversed_list = list(reversed(sorted_points))
reversed_list.append(UH[0])
with Profiler("reversing list"):
reversed_list = list(reversed(sorted_points))
reversed_list.append(UH[0])
LH = reversed_list[:2]
for s in reversed_list[2:]:
while len(LH) > 1 and (sidedness(LH[-2], LH[-1], s) != Side.ABOVE):
del LH[-1]
LH.append(s)
with Profiler("iterating lower hull", excluded=("calculating sidedness",)):
for s in reversed_list[2:]:
while len(LH) > 1 and (sidedness(LH[-2], LH[-1], s) != Side.ABOVE):
del LH[-1]
LH.append(s)
return UH + LH

View File

@ -1,8 +1,8 @@
import random
from math import sqrt
from typing import Set
from util import Side, Point, gen_point, display, gen_circular_point, gen_triangular_point
from profile import Profiler
from util import Side, Point, gen_point, display, gen_circular_point
def sidedness(slope: float, intersection: float, p3: Point, flipper: callable, eps=0.0000001) -> Side:
@ -14,6 +14,7 @@ def sidedness(slope: float, intersection: float, p3: Point, flipper: callable, e
return Side.BELOW
@Profiler("solving 1D LP")
def solve_1dlp(c, constraints):
c1, c2 = c
((a1, a2), b) = constraints[-1]
@ -22,7 +23,6 @@ def solve_1dlp(c, constraints):
interval = [-10_000, 10_000]
for (lel_a1, lel_a2), lel_b in constraints:
bj, aj = (lel_b - lel_a2 * q), (lel_a1 - lel_a2 * p)
if aj < 0 and bj / aj > interval[0]:
interval[0] = bj / aj
@ -36,6 +36,7 @@ def solve_1dlp(c, constraints):
return interval[1], q - (p * interval[1])
@Profiler("solving 2D LP")
def solve_2dlp(c, constraints):
c1, c2 = c
x1 = -10_000 if c1 > 0 else 10_000
@ -47,6 +48,7 @@ def solve_2dlp(c, constraints):
return x1, x2
@Profiler("finding median")
def find_median(points):
num_candidates = min(5, len(points))
candidates = random.sample(points, num_candidates)
@ -59,40 +61,77 @@ def find_median(points):
return median[0]
def mbc_ch(points: Set[Point], flipper: callable) -> Set[Point]:
def is_left(a: Point, b: Point, c: Point):
return ((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)) > 0
def mbc_ch(points: Set[Point], flipper: callable, extra_prune=False, shuffle=True) -> Set[Point]:
if len(points) < 2:
return points
# Extra pruning step
if extra_prune:
with Profiler("extra pruning step"):
left_point = min(points, key=lambda p: (p.x, -p.y))
right_point = max(points, key=lambda p: (p.x, -p.y))
if flipper(1) == 1:
points = {p for p in points if is_left(left_point, right_point, p)}.union({left_point, right_point})
else:
points = {p for p in points if not is_left(left_point, right_point, p)}.union({left_point, right_point})
# Find the point with median x-coordinate, and partition the points on this point
med_x = find_median(points)
# Find left and right points in regards to median
pl = {p for p in points if p.x < med_x}
pr = {p for p in points if p.x >= med_x}
with Profiler("partitioning set"):
pl = {p for p in points if p.x < med_x}
pr = {p for p in points if p.x >= med_x}
# Shuffle
constraints = [((flipper(-p.x), flipper(-1)), flipper(-p.y)) for p in points]
if shuffle:
with Profiler("shuffling constraints"):
random.shuffle(constraints)
# Find the bridge over the vertical line in pm
slope, intercept = solve_2dlp((flipper(med_x), flipper(1)),
[((flipper(-p.x), flipper(-1)), flipper(-p.y)) for p in points])
slope, intercept = solve_2dlp((flipper(med_x), flipper(1)), constraints)
left_point = next(p for p in pl if sidedness(slope, intercept, p, flipper) == Side.ON)
right_point = next(p for p in pr if sidedness(slope, intercept, p, flipper) == Side.ON)
# Find the two points which are on the line
#dist_to_line = lambda p: abs(intercept + slope * p.x - p.y)/sqrt(1 + slope**2)
#left_point = min(pl, key=dist_to_line)
#right_point = min(pr, key=dist_to_line)
with Profiler("finding bridge points"):
left_point = next(p for p in pl if sidedness(slope, intercept, p, flipper) == Side.ON)
right_point = next(p for p in pr if sidedness(slope, intercept, p, flipper) == Side.ON)
# Prune the points between the two line points
pl = {p for p in pl if p.x <= left_point.x}
pr = {p for p in pr if p.x >= right_point.x}
with Profiler("pruning between line points"):
pl = {p for p in pl if p.x <= left_point.x}
pr = {p for p in pr if p.x >= right_point.x}
return set.union(mbc_ch(pl, flipper), {left_point, right_point}, mbc_ch(pr, flipper))
return set.union(mbc_ch(pl, flipper, extra_prune=extra_prune, shuffle=shuffle),
{left_point, right_point},
mbc_ch(pr, flipper, extra_prune=extra_prune, shuffle=shuffle))
@Profiler("mbc")
def mbc(points: Set[Point]) -> Set[Point]:
return set.union(mbc_ch(points, lambda x: x), mbc_ch(points, lambda x: -x))
return set.union(mbc_ch(points, lambda x: x, extra_prune=False, shuffle=True),
mbc_ch(points, lambda x: -x, extra_prune=False, shuffle=True))
@Profiler("mbc2")
def mbc2(points: Set[Point]) -> Set[Point]:
return set.union(mbc_ch(points, lambda x: x, extra_prune=True, shuffle=True),
mbc_ch(points, lambda x: -x, extra_prune=True, shuffle=True))
@Profiler("mbc_no_shuffle")
def mbc_no_shuffle(points: Set[Point]) -> Set[Point]:
return set.union(mbc_ch(points, lambda x: x, extra_prune=False, shuffle=False),
mbc_ch(points, lambda x: -x, extra_prune=False, shuffle=False))
@Profiler("mbc2_no_shuffle")
def mbc2_no_shuffle(points: Set[Point]) -> Set[Point]:
return set.union(mbc_ch(points, lambda x: x, extra_prune=True, shuffle=False),
mbc_ch(points, lambda x: -x, extra_prune=True, shuffle=False))
if __name__ == '__main__':

26
h2/profile.py Normal file
View File

@ -0,0 +1,26 @@
import time
from collections import defaultdict
from contextlib import ContextDecorator
from typing import Tuple
class Profiler(ContextDecorator):
results = defaultdict(float)
def __init__(self, name: str, excluded: Tuple = None) -> None:
self.name = name
self.excluded = excluded or ()
def __enter__(self):
self.start = time.time()
return self
def __exit__(self, *exc):
stop = time.time()
excluded = sum(self.results[e] for e in self.excluded)
self.results[self.name] += stop - self.start - excluded
return False
@classmethod
def reset(cls):
cls.results.clear()

View File

@ -1,6 +1,7 @@
from math import sqrt
from typing import Set
from profile import Profiler
from util import Point, gen_point, display
@ -14,6 +15,7 @@ def is_left(a: Point, b: Point, c: Point):
return ((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)) > 0
@Profiler("quick_hull")
def quick_hull(points: Set[Point]):
left = min(points)
right = max(points)
@ -21,12 +23,16 @@ def quick_hull(points: Set[Point]):
hull = {left, right}
points = points - hull
find_hull({p for p in points if not is_left(left, right, p)},
with Profiler("partitioning set"):
partition = {p for p in points if not is_left(left, right, p)}
find_hull(partition,
left,
right,
hull)
find_hull({p for p in points if not is_left(right, left, p)},
with Profiler("partitioning set"):
partition = {p for p in points if not is_left(right, left, p)}
find_hull(partition,
right,
left,
hull)
@ -38,16 +44,21 @@ def find_hull(points: Set[Point], p: Point, q: Point, hull: Set[Point]):
if not points:
return
farthest = max(points, key=lambda point: abs(distance(p, q, point)))
hull.add(farthest)
points.remove(farthest)
with Profiler("finding farthest point from line"):
farthest = max(points, key=lambda point: abs(distance(p, q, point)))
hull.add(farthest)
points.remove(farthest)
find_hull({po for po in points if not is_left(p, farthest, po)},
with Profiler("partitioning set"):
partition = {po for po in points if not is_left(p, farthest, po)}
find_hull(partition,
p,
farthest,
hull)
find_hull({po for po in points if not is_left(farthest, q, po)},
with Profiler("partitioning set"):
partition = {po for po in points if not is_left(farthest, q, po)}
find_hull(partition,
farthest,
q,
hull)

View File

@ -4,7 +4,8 @@ from collections import namedtuple
import util
from gift_wrapper import rapper
from graham import graham_scan
from mbc import mbc
from mbc import mbc, mbc_no_shuffle, mbc2_no_shuffle, mbc2
from profile import Profiler
from quick_hull import quick_hull
import os.path
@ -13,10 +14,9 @@ import os.path
TimedResult = namedtuple("TimedResult", "algorithm points running_time")
def time_it(f: callable, args: tuple = (), iterations=1):
def time_it(f: callable, args: tuple = ()):
start = time()
for i in range(iterations):
f(*args)
f(*args)
return str(time() - start)
@ -26,7 +26,6 @@ def initiate_file(file):
def write_to_log(file, data):
if not os.path.isfile(file):
initiate_file(file)
@ -42,49 +41,41 @@ def write_to_log(file, data):
open_file.write(write_string)
def do_square_tests(amount_of_points):
points_square = {util.gen_point(0, 100) for _ in range(amount_of_points)}
amount_of_points = str(amount_of_points)
def calculate_hulls(number_of_points, points):
return [TimedResult("graham", number_of_points, time_it(graham_scan, args=(points,))),
TimedResult("gift", number_of_points, time_it(rapper, args=(points,))),
TimedResult("quick", number_of_points, time_it(quick_hull, args=(points,))),
TimedResult("mbch", number_of_points, time_it(mbc, args=(points,))),
TimedResult("mbch2", number_of_points, time_it(mbc2, args=(points,)))]
results = [TimedResult("graham", amount_of_points, time_it(graham_scan, args=(points_square,))),
TimedResult("gift", amount_of_points, time_it(rapper, args=(points_square,))),
TimedResult("quick", amount_of_points, time_it(quick_hull, args=(points_square,))),
TimedResult("mbch", amount_of_points, time_it(mbc, args=(points_square,)))]
def do_square_tests(number_of_points):
points_square = {util.gen_point(0, 100) for _ in range(number_of_points)}
number_of_points = str(number_of_points)
results = calculate_hulls(number_of_points, points_square)
write_to_log("square_tests.log", results)
def do_circular_tests(amount_of_points):
points_circular = {util.gen_point(0, 100) for _ in range(amount_of_points)}
results = [TimedResult("graham", amount_of_points, time_it(graham_scan, args=(points_circular,))),
TimedResult("gift", amount_of_points, time_it(rapper, args=(points_circular,))),
TimedResult("quick", amount_of_points, time_it(quick_hull, args=(points_circular,))),
TimedResult("mbc", amount_of_points, time_it(mbc, args=(points_circular,)))]
def do_circular_tests(number_of_points):
points_circular = {util.gen_point(0, 100) for _ in range(number_of_points)}
results = calculate_hulls(number_of_points, points_circular)
write_to_log("circular_tests.log", results)
def do_triangular_tests(amount_of_points):
def do_triangular_tests(number_of_points):
left, right, top = util.Point(1,1), util.Point(51,1), util.Point(26,40)
points = {util.gen_triangular_point(left, right, top) for _ in range(amount_of_points)}
results = [TimedResult("graham", amount_of_points, time_it(graham_scan, args=(points,))),
TimedResult("gift", amount_of_points, time_it(rapper, args=(points,))),
TimedResult("quick", amount_of_points, time_it(quick_hull, args=(points,))),
TimedResult("mbc", amount_of_points, time_it(mbc, args=(points,)))]
points = {util.gen_triangular_point(left, right, top) for _ in range(number_of_points)}
results = calculate_hulls(number_of_points, points)
write_to_log("triangular_tests.log", results)
def do_quadratic_tests(amount_of_points):
points = {util.gen_weird_point(-10, 10) for _ in range(amount_of_points)}
results = [TimedResult("graham", amount_of_points, time_it(graham_scan, args=(points,))),
TimedResult("gift", amount_of_points, time_it(rapper, args=(points,))),
TimedResult("quick", amount_of_points, time_it(quick_hull, args=(points,))),
TimedResult("mbc", amount_of_points, time_it(mbc, args=(points,)))]
def do_quadratic_tests(number_of_points):
points = {util.gen_weird_point(-10, 10) for _ in range(number_of_points)}
results = calculate_hulls(number_of_points, points)
write_to_log("quadratic_tests.log", results)
@ -94,10 +85,46 @@ def sanity_check():
gift = set(rapper(points))
quick = quick_hull(points)
mbch = set.union(mbc(points))
assert gift == graham == quick == mbch
mbch2 = set.union(mbc2(points))
assert gift == graham == quick == mbch == mbch2
def do_profile():
print("==================================== PROFILE RESULTS ====================================")
random.seed(6)
points = {util.gen_point(0, 100) for _ in range(60_000)}
tests = [
("graham_scan", graham_scan),
("gift_wrapper", rapper),
("quick_hull", quick_hull),
("mbc", mbc),
("mbc2", mbc2),
("mbc_no_shuffle", mbc_no_shuffle),
("mbc2_no_shuffle", mbc2_no_shuffle),
]
for algorithm, func in tests:
Profiler.reset()
func(points)
times = Profiler.results
print(f"-------------- {algorithm} --------------")
print("Times:", times)
total = times[algorithm]
print("Total:", total)
sum_profiled = sum(times.values()) - total
print("Total Profiled:", sum_profiled)
print("Unaccounted:", total - sum_profiled)
if __name__ == '__main__':
sanity_check()
do_profile()
exit()
for i in range(50, 1000, 50):
do_square_tests(i)

View File

@ -72,6 +72,7 @@ def gen_graph(data):
graham = data['graham']
quick = data['quick']
mbch = data['mbch']
mbch2 = data['mbch2']
gift = data['gift']
graham_x = [p[0] for p in graham]
@ -83,15 +84,19 @@ def gen_graph(data):
mbch_x = [p[0] for p in mbch]
mbch_y = [p[1] for p in mbch]
mbch2_x = [p[0] for p in mbch2]
mbch2_y = [p[1] for p in mbch2]
gift_x = [p[0] for p in gift]
gift_y = [p[1] for p in gift]
plt.plot(graham_x, graham_y)
plt.plot(quick_x, quick_y)
plt.plot(mbch_x, mbch_y)
plt.plot(mbch2_x, mbch2_y)
plt.plot(gift_x, gift_y)
plt.legend(['graham', 'quick', 'mbch', 'gift'], loc='upper left')
plt.legend(['graham', 'quick', 'mbch', 'mbch2', 'gift'], loc='upper left')
plt.show()