98 lines
3.2 KiB
Python
98 lines
3.2 KiB
Python
import statistics
|
|
from math import inf
|
|
from typing import Set, List, Tuple
|
|
|
|
from util import Side, Point, gen_point, display
|
|
|
|
|
|
def sidedness(slope: float, intersection: float, p3: Point, linprog_flipper: callable, eps=0.0000001) -> Side:
|
|
# finds where a point is in regards to a line
|
|
if linprog_flipper(p3.y) - eps <= linprog_flipper(slope * p3.x + intersection) <= linprog_flipper(p3.y) + eps:
|
|
return Side.ON
|
|
elif p3.y > slope * p3.x + intersection:
|
|
return Side.ABOVE
|
|
return Side.BELOW
|
|
|
|
|
|
def solve_1dlp(c: float, constraints: List[Tuple[float, float]]):
|
|
"""
|
|
:param c: c1
|
|
:param constraints: [(ai, bi), ...]
|
|
:return: x1
|
|
"""
|
|
try:
|
|
if c > 0:
|
|
return max(b/a for a, b in constraints if a < 0)
|
|
return min(b/a for a, b in constraints if a > 0)
|
|
except ValueError: # unbounded
|
|
return -inf if c > 0 else inf
|
|
|
|
|
|
assert solve_1dlp(1, [(-1, -2)]) == 2
|
|
assert solve_1dlp(1, [(-1, -2), (-1, -3)]) == 3
|
|
assert solve_1dlp(1, [(-1, -3), (-1, -2)]) == 3
|
|
assert solve_1dlp(-1, [(1, 3), (1, 2)]) == 2
|
|
assert solve_1dlp(1, [(-1, 3), (-1, 2)]) == -2
|
|
|
|
|
|
def solve_2dlp(c: Tuple[float, float], constraints: List[Tuple[Tuple[float, float], float]]):
|
|
"""
|
|
:param c: (c1, c2)
|
|
:param constraints: [(ai1, ai2, bi), ...]
|
|
:return: x1, x2
|
|
"""
|
|
c1, c2 = c
|
|
x1, x2 = (-inf, -inf) if c1 > 0 else (inf, inf)
|
|
#random.shuffle(constraints)
|
|
|
|
for i, ((a1, a2), b) in enumerate(constraints[1:], start=1):
|
|
if a1*x1 + a2*x2 <= b:
|
|
continue
|
|
|
|
x1 = solve_1dlp(c1 - c2*a1/a2, [(ai1 - ai2*a1 / a2, bi - ai2*b / a2) for (ai1, ai2), bi in constraints[:i]])
|
|
x2 = (b - a1*x1) / a2
|
|
|
|
return x1, x2
|
|
|
|
|
|
def mbc_ch(points: Set[Point], linprog_flipper: callable) -> Set[Point]:
|
|
if len(points) <= 2:
|
|
return points
|
|
|
|
# Find the point with median x-coordinate, and partition the points on this point
|
|
med_x = statistics.median(p.x for p in 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}
|
|
|
|
# Find the bridge over the vertical line in pm
|
|
c = (linprog_flipper(med_x), linprog_flipper(1))
|
|
A = [(linprog_flipper(-p.x), linprog_flipper(-1)) for p in points]
|
|
b = [linprog_flipper(-p.y) for p in points]
|
|
|
|
#result = linprog(c, A, b, bounds=[(None, None), (None, None)], options={"tol": 0.00001})
|
|
#slope, intercept = result.x[0], result.x[1]
|
|
|
|
slope, intercept = solve_2dlp(c, list(zip(A, b)))
|
|
|
|
#display_line_only(points, slope, intercept, [])
|
|
|
|
# Find the two points which are on the line, should work
|
|
left_point = next(p for p in pl if sidedness(slope, intercept, p, linprog_flipper) == Side.ON)
|
|
right_point = next(p for p in pr if sidedness(slope, intercept, p, linprog_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}
|
|
|
|
return set.union(mbc_ch(pl, linprog_flipper), {left_point, right_point}, mbc_ch(pr, linprog_flipper))
|
|
|
|
|
|
points = {gen_point(1, 10) for _ in range(20)}
|
|
|
|
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))
|