BerGeo/h2/mbc.py

149 lines
5.6 KiB
Python
Raw Normal View History

2018-10-16 13:54:44 +00:00
import random
2018-10-18 09:17:30 +00:00
from typing import Set
2018-10-08 13:17:02 +00:00
2018-10-18 16:41:18 +00:00
from profile import Profiler
from util import Side, Point, gen_point, display, gen_circular_point
2018-09-24 13:27:08 +00:00
2018-10-11 13:54:52 +00:00
def sidedness(slope: float, intersection: float, p3: Point, flipper: callable, eps=0.0000001) -> Side:
2018-10-09 17:26:55 +00:00
# finds where a point is in regards to a line
2018-10-11 13:54:52 +00:00
if flipper(p3.y) - eps <= flipper(slope * p3.x + intersection) <= flipper(p3.y) + eps:
2018-10-08 13:17:02 +00:00
return Side.ON
elif p3.y > slope * p3.x + intersection:
return Side.ABOVE
return Side.BELOW
2018-10-18 09:17:30 +00:00
def solve_1dlp(c, constraints):
2018-10-16 13:54:44 +00:00
c1, c2 = c
((a1, a2), b) = constraints[-1]
2018-10-18 09:17:30 +00:00
q, p = b / a2, a1 / a2
2018-10-16 13:54:44 +00:00
2018-10-18 09:17:30 +00:00
interval = [-10_000, 10_000]
2018-10-16 13:54:44 +00:00
2018-10-18 09:17:30 +00:00
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
elif aj > 0 and bj / aj < interval[1]:
2018-10-18 09:17:30 +00:00
interval[1] = bj / aj
2018-10-16 13:54:44 +00:00
c = -(c1 - c2 * p)
if c < 0:
return interval[0], q - (p * interval[0])
elif c >= 0:
return interval[1], q - (p * interval[1])
2018-10-21 14:59:17 +00:00
@Profiler("solving LP")
2018-10-18 09:17:30 +00:00
def solve_2dlp(c, constraints):
2018-10-09 17:26:55 +00:00
c1, c2 = c
2018-10-18 09:17:30 +00:00
x1 = -10_000 if c1 > 0 else 10_000
x2 = -10_000 if c2 > 0 else 10_000
2018-10-16 13:54:44 +00:00
2018-10-18 09:17:30 +00:00
for i, ((a1, a2), b) in enumerate(constraints, start=1):
2018-10-16 13:54:44 +00:00
if not (a1*x1 + a2*x2 <= b):
2018-10-18 09:17:30 +00:00
x1, x2 = solve_1dlp(c, constraints[:i])
return x1, x2
2018-10-18 16:41:18 +00:00
@Profiler("finding median")
2018-10-16 13:54:44 +00:00
def find_median(points):
num_candidates = min(5, len(points))
candidates = random.sample(points, num_candidates)
candidates.sort(key=lambda p: p.x)
median_i = num_candidates // 2
median = ((candidates[median_i - 1].x + candidates[median_i].x)/2,
(candidates[median_i - 1].y + candidates[median_i].y)/2)
return median[0]
2018-10-18 16:41:18 +00:00
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]:
2018-10-16 13:54:44 +00:00
if len(points) < 2:
2018-10-08 13:17:02 +00:00
return points
2018-10-18 16:41:18 +00:00
# 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})
2018-10-08 13:17:02 +00:00
# Find the point with median x-coordinate, and partition the points on this point
2018-10-16 13:54:44 +00:00
med_x = find_median(points)
2018-10-08 13:17:02 +00:00
# Find left and right points in regards to median
2018-10-18 16:41:18 +00:00
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
2018-10-21 14:59:17 +00:00
with Profiler("flipping constraints"):
constraints = [((flipper(-p.x), flipper(-1)), flipper(-p.y)) for p in points]
2018-10-18 16:41:18 +00:00
if shuffle:
with Profiler("shuffling constraints"):
random.shuffle(constraints)
2018-09-24 13:27:08 +00:00
# Find the bridge over the vertical line in pm
2018-10-18 16:41:18 +00:00
slope, intercept = solve_2dlp((flipper(med_x), flipper(1)), constraints)
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)
2018-10-16 13:54:44 +00:00
2018-10-18 16:41:18 +00:00
# Prune the points between the two line points
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}
2018-10-16 13:54:44 +00:00
2018-10-18 16:41:18 +00:00
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))
2018-10-16 13:54:44 +00:00
2018-10-18 16:41:18 +00:00
@Profiler("mbc")
def mbc(points: Set[Point]) -> Set[Point]:
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))
2018-10-08 13:17:02 +00:00
2018-10-18 16:41:18 +00:00
@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))
2018-10-08 13:17:02 +00:00
2018-10-18 16:41:18 +00:00
@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))
2018-10-08 13:17:02 +00:00
2018-10-11 12:38:59 +00:00
if __name__ == '__main__':
random.seed(1337_420)
points = {gen_point(0, 20) for _ in range(20)}
2018-10-18 09:17:30 +00:00
points = {gen_circular_point(1, 100, 50) for _ in range(200)}
#points = {gen_triangular_point(Point(1,1), Point(51,1), Point(26, 30)) for _ in range(200)}
2018-10-16 13:54:44 +00:00
#points = {Point(x=-33.11091053638924, y=38.88967778961347), Point(x=61.20269947424177, y=-78.96305419217254), Point(x=99.44053842147957, y=-89.11579172297581), Point(x=-92.40380889537532, y=84.33904351991652), Point(x=-90.63139185545595, y=-91.13793846505985)}
#points = {Point(x=5.2, y=9.7), Point(x=5.3, y=4.9), Point(x=3.3, y=3.6), Point(x=9.2, y=4.8), Point(x=9.7, y=5.7), Point(x=5.6, y=8.7)}
2018-10-11 12:38:59 +00:00
upper_hull_points = mbc_ch(points, lambda x: x)
lower_hull_points = mbc_ch(points, lambda x: -x)
display(points, upper_hull_points.union(lower_hull_points))