From e3b3149a96e69001627db63948168de6da8b21a0 Mon Sep 17 00:00:00 2001 From: Artyom Fisunenko Date: Mon, 23 Jan 2023 12:06:28 +0200 Subject: [PATCH] add dist --- .github/ISSUE_TEMPLATE/bug_report.md | 38 -- README.md | 28 +- __init__.py | 0 algo/__init__.py | 0 algo/chain_method.py | 150 ------- algo/dc_closest_points.py | 113 ----- algo/graham.py | 53 --- algo/jarvis.py | 29 -- algo/kd_tree_method.py | 18 - algo/loci.py | 49 --- algo/quickhull.py | 60 --- algo/region_tree_method.py | 13 - algo/stripe_method.py | 87 ---- algo/triangulation_refinement.py | 11 - models/__init__.py | 0 models/bin_tree.py | 152 ------- models/bin_tree_node.py | 40 -- models/edge.py | 23 - models/graph.py | 44 -- models/hull.py | 45 -- models/line2d.py | 22 - models/point.py | 112 ----- models/polygon.py | 46 -- models/region_tree.py | 66 --- models/triangle.py | 21 - models/vector.py | 59 --- models/vertex.py | 18 - pyproject.toml | 3 + setup.cfg | 19 + src/CGLib | 1 + tests/__init__.py | 0 tests/test_algorithms.py | 625 --------------------------- tests/test_models.py | 133 ------ 33 files changed, 24 insertions(+), 2054 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 __init__.py delete mode 100644 algo/__init__.py delete mode 100644 algo/chain_method.py delete mode 100644 algo/dc_closest_points.py delete mode 100644 algo/graham.py delete mode 100644 algo/jarvis.py delete mode 100644 algo/kd_tree_method.py delete mode 100644 algo/loci.py delete mode 100644 algo/quickhull.py delete mode 100644 algo/region_tree_method.py delete mode 100644 algo/stripe_method.py delete mode 100644 algo/triangulation_refinement.py delete mode 100644 models/__init__.py delete mode 100644 models/bin_tree.py delete mode 100644 models/bin_tree_node.py delete mode 100644 models/edge.py delete mode 100644 models/graph.py delete mode 100644 models/hull.py delete mode 100644 models/line2d.py delete mode 100644 models/point.py delete mode 100644 models/polygon.py delete mode 100644 models/region_tree.py delete mode 100644 models/triangle.py delete mode 100644 models/vector.py delete mode 100644 models/vertex.py create mode 100644 pyproject.toml create mode 100644 setup.cfg create mode 160000 src/CGLib delete mode 100644 tests/__init__.py delete mode 100644 tests/test_algorithms.py delete mode 100644 tests/test_models.py diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index dd84ea7..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: '' -assignees: '' - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Desktop (please complete the following information):** - - OS: [e.g. iOS] - - Browser [e.g. chrome, safari] - - Version [e.g. 22] - -**Smartphone (please complete the following information):** - - Device: [e.g. iPhone6] - - OS: [e.g. iOS8.1] - - Browser [e.g. stock browser, safari] - - Version [e.g. 22] - -**Additional context** -Add any other context about the problem here. diff --git a/README.md b/README.md index 76b14e8..311c95c 100644 --- a/README.md +++ b/README.md @@ -1,27 +1 @@ -# Computational geometry algotrithms module -[![Codacy Badge](https://app.codacy.com/project/badge/Grade/9630c9cb0ce44acdb3d2f4c15e27cde7)](https://www.codacy.com/gh/Chmele/geometry-module/dashboard?utm_source=github.com&utm_medium=referral&utm_content=Chmele/geometry-module&utm_campaign=Badge_Grade) - -## Contents -This module contains implementations for computational geometry algorithms based upon Franco P. Preparata and Michael I. Shamos' book "Computational Geometry: An Introduction". These algorithms are subdivided into three topics: geometric searching, constructing convex hulls, and proximity problems. -#### Geometric searching -* Point location - * *Slab method*: locate a point in a planar graph between its two edges. - * *Chain method*: locate a point in a planar graph between its two monotone chains connecting its lower-most and upper-most vertices. - * *Triangulation refinement method **(TBD)***: locate a point in a triangulated planar graph in one of the triangles. -* Range-searching - * *k-D tree method*: find out which or how many points of a given set lie in a specified range, using a multidimensional binary tree (here, 2-*D* tree). - * *Range-tree method **(TBD)***: find out which or how many points of a given set lie in a specified range, using a range tree data structure. - * *Loci method*: find out how many points of a given set lie in a specified range, using a region (locus) partition of the searching space. -#### Constructing convex hulls -* Static problem - * *Graham's scan*: construct the convex hull of a given set of points, using a stack of points. - * *Quickhull*: construct the convex hull of a given set of points, using the partitioning of the set and merging the subsets similar to those in Quicksort algorithm. - * *Divide-and-conquer*: given the convex hulls of the two subsets of a given set of points, merge them into a convex hull of the entire set of points. - * *Jarvis' march*: construct the convex hull of a given set of points, using the so-called gift wrapping technique. -* Dynamic problem - * *Preparata's algorithm **(TBD)***: construct the convex hull of a set of points being dynamically added to a current hull. - * *Dynamic convex hull maintenance **(TBD)***: construct the convex hull of a set of points and re-construct it on addition or deletion of a point. -#### Proximity problems -* *Divide-and-conquer closest pair search **(TBD)***: given a set of points, find the two points with the smallest mutual distance, using divide-and-conquer approach. -* *Divide-and-conquer Voronoi diagram constructing **(TBD)***: given a set points, construct their Voronoi diagram, using divide-and-conquer approach. - +Computational geometry algorithms library \ No newline at end of file diff --git a/__init__.py b/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/algo/__init__.py b/algo/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/algo/chain_method.py b/algo/chain_method.py deleted file mode 100644 index ed9512c..0000000 --- a/algo/chain_method.py +++ /dev/null @@ -1,150 +0,0 @@ -from math import pi -from collections import OrderedDict -from ..models.point import Point -from ..models.bin_tree_node import NodeWithParent -from ..models.bin_tree import ChainsBinTree -from ..models.graph import OrientedGraph - - -def chain_method(graph: OrientedGraph, point: Point): - if not graph.is_regular(): - return (None, None) - else: - yield graph.sorted_vertices(sort_key=lambda v: v.point.y) - - weight_table = make_weight_table(graph) - yield weight_table - - balance(weight_table, is_down=True) - balance(weight_table, is_down=False) - yield weight_table - - chains = create_chains(weight_table) - yield chains - - root = NodeWithParent(data=chains[len(chains) // 2]) - tree = ChainsBinTree(root) - tree.make_tree(chains, root) - yield tree - - yield tree.search_point(point) - - -def make_weight_table(graph: OrientedGraph): - """ - Make weighted table for graph: - "vin" - "in" edges of a vertex sorted counterclockwise - "vout" - "out" edges of a vertex sorted clockwise - "win" - sum of vin edges' weights - "wout" - sum of vout edges' weights - """ - weight_table = OrderedDict() - for vertex in graph.sorted_vertices(sort_key=lambda v: v.point.y): - vertex_data = {} - - vin = list(filter(lambda e: e.v2 == vertex, graph.edges)) - vout = list(filter(lambda e: e.v1 == vertex, graph.edges)) - - vin = sort_v_edges(vin, is_out=False) - vout = sort_v_edges(vout, is_out=True) - - vertex_data["vin"] = vin - vertex_data["vout"] = vout - - weight_v_edges(vertex_data, vin, "win") - weight_v_edges(vertex_data, vout, "wout") - - weight_table[vertex] = vertex_data - - return weight_table - - -def sort_v_edges(edges, is_out): - '''Sort vin edges counterclockwise, vout edges clockwise''' - if not edges: - return edges - - def v_angle(p1, p2): - '''Counterclockwise polar angle to sort by''' - angle = p1.ccw_polar_angle_with(p2) - print(str(p1), str(p2)) - if is_out: - return angle if angle < 3 * pi / 2 else angle - 2 * pi - else: - return angle if angle >= pi / 2 else 2 * pi + angle - - if is_out: - sort_key = lambda e: v_angle(e.v2.point, e.v1.point) - else: - sort_key = lambda e: v_angle(e.v1.point, e.v2.point) - - return sorted(edges, key=sort_key, reverse=is_out) - - -def weight_v_edges(vertex_data, edges, edge_type): - '''Set total weight of either vin or vout edges of the vertex''' - if not edges: - vertex_data[edge_type] = 0 - else: - vertex_data[edge_type] = sum(e.weight for e in edges) - - -def balance(weight_table: OrderedDict, is_down): - ''' - Balance weight table by traversing it forwards and backwards - and reassigning weight values. - ''' - if is_down: - v_type1, v_type2, w_type1, w_type2 = "vin", "vout", "win", "wout" - vertex_data = weight_table.values() - else: - v_type1, v_type2, w_type1, w_type2 = "vout", "vin", "wout", "win" - vertex_data = reversed(weight_table.values()) - - update_edge_weights(vertex_data, v_type1, v_type2, w_type1, w_type2) - - -def update_edge_weights(vertex_data, v_type1, v_type2, w_type1, w_type2): - rows = list(vertex_data) - for vd in rows[1:-1]: - if vd[w_type1] > vd[w_type2]: - edge_to_update = vd[v_type2][0] - old_weight = edge_to_update.weight - edge_to_update.weight = vd[w_type1] - vd[w_type2] + 1 - delta_weight = edge_to_update.weight - old_weight - vd[w_type2] += delta_weight - - for x in rows: - for e in x[v_type1]: - if e == edge_to_update: - x[w_type1] += delta_weight - - -def create_chains(weight_table: OrderedDict): - '''Create monotone chains from graph's start to its end''' - chain, chains = [], [] - current = None - - first = list(weight_table.keys())[0] - last = list(weight_table.keys())[-1] - while weight_table.get(first)["wout"] != 0: - current = first - - while current != last: - edge = weight_table[current]["vout"][0] - - if edge.weight == 0: - weight_table[current]["vout"].pop(0) - edge = weight_table[current]["vout"][0] - - chain.append(edge) - edge.weight -= 1 - weight_table[current]["wout"] -= 1 - weight_table[edge.v2]["win"] -= 1 - current = edge.v2 - - chains.append(chain) - chain = [] - current = first - - return chains diff --git a/algo/dc_closest_points.py b/algo/dc_closest_points.py deleted file mode 100644 index 67d5716..0000000 --- a/algo/dc_closest_points.py +++ /dev/null @@ -1,113 +0,0 @@ -from sys import maxsize -from typing import Tuple, List -from ..models.point import Point - -def closest_points(points: List[Point]): - Ux, Uy = init_points(points) - return closest_pair(Ux, Uy) - - -def init_points(points: List[Point]): - Ux = sorted(points, key=lambda point: point.x) - Uy = sorted(points, key=lambda point: point.y) - #U = {point: {"x": Ux.index(point), "j": Uy.index(point)} for point in points} - return Ux, Uy - - -def closest_pair_split( - points_sorted_x: List[Point], points_sorted_y: List[Point], delta: float -): - """Finding closest pair of points splited by the line""" - length_x = len(points_sorted_x) - middle = length_x // 2 - middle_point = points_sorted_x[middle] - points_sorted_x = list( - filter( - lambda point: point.dist_to_point(middle_point) <= delta, points_sorted_x - ) - ) - left_points_x = list( - filter(lambda point: point.x <= middle_point.x, points_sorted_x) - ) - right_points_x = list( - filter(lambda point: point.x > middle_point.x, points_sorted_x) - ) - - left_points_x.sort(key=lambda point: point.y) - right_points_x.sort(key=lambda point: point.y) - - min_dist_pairs: List[Tuple] = [] - - for point_left in left_points_x: - in_range = list( - filter( - lambda point_right: abs(point_right.y - point_left.y) < delta, - right_points_x, - ) - ) - if in_range: - min_dist_pair = ( - point_left, - min(in_range, key=lambda point: point_left.dist_to_point(point)), - ) - min_dist_pairs.append(min_dist_pair) - - if min_dist_pairs: - return min(min_dist_pairs, key=lambda pair: pair[0].dist_to_point(pair[1])) - else: - return None - - -def closest_pair(points_sorted_x: List[Point], points_sorted_y: List[Point]): - """Finding closest pair of points""" - length_x = len(points_sorted_x) - dist_left, dist_right, dist_split = 3*[maxsize] - - if length_x < 2: - return None - elif length_x == 2: - point1, point2 = points_sorted_x - return (point1, point2) - else: - middle = length_x // 2 - middle_point = points_sorted_x[middle] - - left_sorted_y = sorted( - list(filter(lambda point: point.x < middle_point.x, points_sorted_y)), - key=lambda p: p.y, - ) - - right_sorted_y = sorted( - list(filter(lambda point: point.x >= middle_point.x, points_sorted_y)), - key=lambda p: p.y - ) - - closest_points_left: Tuple[Point] = closest_pair( - points_sorted_x[:middle], left_sorted_y - ) - closest_points_right: Tuple[Point] = closest_pair( - points_sorted_x[middle:], right_sorted_y - ) - - if closest_points_left: - dist_left = closest_points_left[0].dist_to_point(closest_points_left[1]) - - if closest_points_right: - dist_right = closest_points_right[0].dist_to_point(closest_points_right[1]) - - delta = min(dist_left, dist_right) - - closest_points_split: Tuple[Point] = closest_pair_split( - points_sorted_x, points_sorted_y, delta - ) - - if closest_points_split: - dist_split = closest_points_split[0].dist_to_point(closest_points_split[1]) - - minimum = min(dist_left, dist_right, dist_split) - if minimum == dist_left: - return closest_points_left - elif minimum == dist_right: - return closest_points_right - else: - return closest_points_split diff --git a/algo/graham.py b/algo/graham.py deleted file mode 100644 index 45e7d3a..0000000 --- a/algo/graham.py +++ /dev/null @@ -1,53 +0,0 @@ -from math import pi -from ..models.point import Point - - -def graham(points): - i = 2 - while Point.direction(points[0], points[1], points[i]) == 0: - i += 1 - - centroid = Point.centroid([points[0], points[1], points[i]]) - yield centroid - - origin = min(points, key=lambda p: (p.y, -p.x)) - ordered = sort_points(points, centroid, origin) - yield ordered - yield origin - - ordered.append(origin) - steps_table = [] - hull = make_hull(steps_table, ordered) - ordered.pop() - yield steps_table - yield hull - - -def sort_points(points, centroid, origin): - min_angle = origin.polar_angle_with(centroid) - - def angle_and_dist(p): - p_angle = p.polar_angle_with(centroid) - angle = p_angle if p_angle >= min_angle else 2 * pi + p_angle - return (angle, p.dist_to_point(centroid)) - - return sorted(points, key=angle_and_dist) - - -def make_hull(steps_table, ordered): - ans = ordered[:2] - for p in ordered[2:]: - while len(ans) > 1 and Point.direction(ans[-2], ans[-1], p) >= 0: - steps_table.append(current_step(ans, False, p)) - ans.pop() - - if len(ans) > 1: - steps_table.append(current_step(ans, True, p)) - ans.append(p) - - return ans[:-1] - - -def current_step(ans, add, p): - """Current step: current points' triple, add/delete, point to add/delete.""" - return [ans[-2], ans[-1], p], add diff --git a/algo/jarvis.py b/algo/jarvis.py deleted file mode 100644 index 756e994..0000000 --- a/algo/jarvis.py +++ /dev/null @@ -1,29 +0,0 @@ -from ..models.point import Point - - -def jarvis(points): - ind = points.index(min(points, key=lambda p: p.x)) - lm, ans, length = ind, [points[ind]], len(points) - - while True: - nxt = (lm + 1) % length - for i in range(length): - if i != lm and direction_correct(points, lm, i, nxt): - nxt = i - lm = nxt - - if lm == ind: - break - ans.append(points[nxt]) - - return ans - - -def direction_correct(points, i1, i2, i3): - p1, p2, p3 = points[i1], points[i2], points[i3] - d = Point.direction(p1, p2, p3) - return ( - d > 0 or - d == 0 and - p1.dist_to_point(p2) > p1.dist_to_point(p3) - ) diff --git a/algo/kd_tree_method.py b/algo/kd_tree_method.py deleted file mode 100644 index b398bbd..0000000 --- a/algo/kd_tree_method.py +++ /dev/null @@ -1,18 +0,0 @@ -from ..models.bin_tree_node import Node -from ..models.bin_tree import KdTree - - -def kd_tree(points, x_range, y_range): - ordered_x = sorted(points) - ordered_y = sorted(points, key=lambda p: (p.y, p.x)) - yield ordered_x, ordered_y - - root = Node(ordered_x[len(ordered_x) // 2]) - tree = KdTree(root, x_range, y_range) - tree.make_tree(ordered_x, root) - yield tree.partition - yield tree - - result = tree.region_search(root, vertical=True) - yield tree.search_list - yield result diff --git a/algo/loci.py b/algo/loci.py deleted file mode 100644 index ea0703d..0000000 --- a/algo/loci.py +++ /dev/null @@ -1,49 +0,0 @@ -from ..models.point import Point - - -class Loci: - """Is a wrap of a dict {Point: int}, int is amount of points smaller than key point""" - - def __init__(self): - """By default constructor creates null instance.""" - self.repr = {} - - def append_points(self, *li): - for point in li: - self.append_point(point) - - def append_point(self, point): - self.repr.update({point: self.query(point) + 1}) - self.repr.update( - {p: self.query(p) + 1 for p in self.get_dominating_points(point, 0)} - ) - self.repr.update( - {p: self.query(p) + 1 for p in self.get_dominating_points(point, 1)} - ) - - def query(self, point): - mins = list(filter(point.dominating, self.repr)) - if mins: - return self.repr[max(mins)] - return 0 - - def get_dominating_points(self, point, dimension): - dim_coords = (i[dimension] for i in self.repr if point[dimension] < i[dimension]) - p_coords = list(point.coords) - - def new_dot(value): - ret = list(p_coords) - ret[dimension] = value - return Point(*ret) - - return map(new_dot, dim_coords) - - def get_points_in_rect(self, rect): - x, y = rect - q = self.query - return ( - q(Point(x[1], y[1])) - - q(Point(x[0], y[1])) - - q(Point(x[1], y[0])) - + q(Point(x[0], y[0])) - ) diff --git a/algo/quickhull.py b/algo/quickhull.py deleted file mode 100644 index d32dc3c..0000000 --- a/algo/quickhull.py +++ /dev/null @@ -1,60 +0,0 @@ -from ..models.line2d import Line2D -from ..models.bin_tree_node import QuickhullNode -from ..models.bin_tree import BinTree -from ..models.point import Point - - -sort_lr = lambda p: (p.x, -p.y) -sort_rl = lambda p: (-p.x, p.y) - - -def quickhull(points): - lp = min(points, key=lambda p: p.coords) - rp = max(points, key=lambda p: p.coords) - - s1 = make_subset(points, lp, rp, sort_key=sort_lr) - s2 = make_subset(points, rp, lp, sort_key=sort_rl) - - tree = BinTree(QuickhullNode(s1 + s2[1:-1])) - tree.root.left, tree.root.right = QuickhullNode(s1), QuickhullNode(s2) - - hull = ( - partition(s1, lp, rp, tree.root.left) + - partition(s2, rp, lp, tree.root.right)[1:-1] - ) - tree.root.data.hull_piece = hull - - yield lp, rp, s1, s2, tree - yield tree - yield hull - - -def partition(points, left, right, node): - if len(points) == 2: - node.data.hull_piece = [left, right] - return node.data.hull_piece - - lr = Line2D(left, right) - pts = filter(lambda x: x != left and x != right, points) - - h = max(pts, key=lambda p: (p.dist_to_line(lr), p.angle_with(left, right))) - s1 = left_points(points, left, h) - s2 = left_points(points, h, right) - - node.data.h, node.left, node.right = h, QuickhullNode(s1), QuickhullNode(s2) - node.data.hull_piece = partition(s1, left, h, node.left) + partition(s2, h, right, node.right)[1:] - - return node.data.hull_piece - - -def make_subset(points, left, right, sort_key): - return sorted(left_points(points, left, right), key=sort_key) - - -def left_points(points, p1, p2): - """Points at the left of vector p1->p2 and p1, p2""" - return ( - [p1] + - [p for p in points if Point.direction(p1, p2, p) < 0] + - [p2] - ) diff --git a/algo/region_tree_method.py b/algo/region_tree_method.py deleted file mode 100644 index f4ce54e..0000000 --- a/algo/region_tree_method.py +++ /dev/null @@ -1,13 +0,0 @@ -from ..models.region_tree import RegionTree - - -def region_tree_method(points, x_range, y_range): - reg_tree = RegionTree(points) - yield reg_tree.x_ordered, reg_tree.y_ordered - yield reg_tree.projections - yield reg_tree - x_norm = RegionTree.region_normalization(reg_tree.projections, x_range) - yield x_norm - nodes = RegionTree.primary_search(reg_tree.root, x_norm) if x_norm else None - yield nodes - yield [RegionTree.secondary_search(node.data[1], y_range) for node in nodes] diff --git a/algo/stripe_method.py b/algo/stripe_method.py deleted file mode 100644 index ff57399..0000000 --- a/algo/stripe_method.py +++ /dev/null @@ -1,87 +0,0 @@ -import functools as f -from ..models.point import Point -from ..models.graph import Graph - - -def stripe(g: Graph, dot: Point): - """Stripe method for dot localization.""" - separators = sorted(set(map(lambda x: x[1], g.vertices))) - separators = [float("-inf")] + separators + [float("inf")] - stripes = [] - for separ in range(len(separators) - 1): - stripes.append((separators[separ], separators[separ + 1])) - yield stripes - table = first_stage(stripes, g) - yield table - stripe = find_stripe(stripes, dot) - yield stripe - edges_to_check = sorted_edges_in_stripe(table[stripe], stripe) - yield check_edges(edges_to_check, dot) - - -def edge_value_in_y(edge, y): - x1, y1 = edge.v1.point.coords - x2, y2 = edge.v2.point.coords - - return (x2 - x1) * (y - y1) / (y2 - y1) + x1 - - -def sorted_edges_in_stripe(edges, stripe): - stripe_median = sum(stripe) / 2 - return sorted(edges, key=f.partial(edge_value_in_y, y=stripe_median)) - - -def edge_in_stripe(self, stripe): - """True if edge y projection overlaps stripe y region.""" - return ( - self.v1.point.y <= stripe[0] - and self.v2.point.y >= stripe[1] - or self.v2.point.y <= stripe[0] - and self.v1.point.y >= stripe[1] - ) - - -def position_dot_edge(dot, edge): - """Vector magic... - - * / -> positive(dot in left) - / * -> negative(dor in right) - * is on / -> 0 - """ - x1, y1 = edge.v1.point.coords - x2, y2 = edge.v2.point.coords - x3, y3 = dot.coords - - return (x3 - x1) * (y2 - y1) - (y3 - y1) * (x2 - x1) - - -def first_stage(stripes, g: Graph): - """Return list of tuples (lower, upper) bounds for each stripes.""" - ans = {} - for stripe in stripes: - ans.update({stripe: list(filter(lambda x: edge_in_stripe(x, stripe), g.edges))}) - return ans - - -def dot_in_stripe(dot, stripe): - """True if dot.y is in horizontal stripe.""" - return stripe[0] < dot.y <= stripe[1] - - -def find_stripe(stripes, dot): - """Return stripe in which dot is located from stripe list.""" - return filter(lambda x: dot_in_stripe(dot, x), stripes).__next__() - - -def dot_between_edges(dot, edges): - """True if dot is in left of one edge and right of another.""" - return position_dot_edge(dot, edges[0]) * position_dot_edge(dot, edges[1]) < 0 - - -def check_edges(edges, dot): - """Return pair of edges, if dot is between them.""" - tuples = [] - for edge in range(len(edges) - 1): - tuples.append((edges[edge], edges[edge + 1])) - ans = filter(lambda x: dot_between_edges(dot, x), tuples).__next__() - return list(ans) diff --git a/algo/triangulation_refinement.py b/algo/triangulation_refinement.py deleted file mode 100644 index dc4dee1..0000000 --- a/algo/triangulation_refinement.py +++ /dev/null @@ -1,11 +0,0 @@ -from ..models.graph import Graph -from ..models.point import Point -from ..models.triangle import Triangle - - -class Kirkpatrick: - def __init__(self, g: Graph): - pass - - def locate_point(self, dot: Point): - pass diff --git a/models/__init__.py b/models/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/models/bin_tree.py b/models/bin_tree.py deleted file mode 100644 index 969bfcd..0000000 --- a/models/bin_tree.py +++ /dev/null @@ -1,152 +0,0 @@ -from .bin_tree_node import Node, NodeWithParent -from .point import Point - - -class BinTree: - def __init__(self, root: Node): - self.root = root - - def __eq__(self, other): - return self.root == other.root - - @property - def nodes(self): - """ - Returns the tree represented as left-to-right list of tuples - with nodes' data, and the data of their left and right children. - """ - return self._nodes(self.root) - - def _nodes(self, node, result=None): - if result is None: - result = [] - - left_data = node.left.data if node.left else None - right_data = node.right.data if node.right else None - - if node: - result.append((node.data, left_data, right_data)) - if node.left: - self._nodes(node.left, result) - if node.right: - self._nodes(node.right, result) - - return result - - -class KdTree(BinTree): - def __init__(self, root: Node, x_range, y_range): - super().__init__(root) - self.x_range = x_range - self.y_range = y_range - self.partition = [] - self.search_list = [] - - def make_tree(self, points, node: Node, vertical=True): - med = len(points) // 2 - part = (points[med], vertical) - - if all(p[0] != part[0] for p in self.partition): - self.partition.append(part) - - if med == 0: - return - - if vertical: - sort_key = lambda p: p.y - else: - sort_key = lambda p: p.x - - list_l = sorted(points[:med], key=sort_key) - list_r = sorted(points[-med:], key=sort_key) - left, right = list_l[med // 2], list_r[med // 2] - - node.left = Node(left) - if node.data != right: - node.right = Node(right) - - self.make_tree(list_l, node.left, not vertical) - self.make_tree(list_r, node.right, not vertical) - - def region_search(self, node: Node, vertical=True): - if vertical: - left, right, coord = self.x_range[0], self.x_range[1], node.data.x - else: - left, right, coord = self.y_range[0], self.y_range[1], node.data.y - - dots = [] - to_add = self.dot_in_region(node.data) - - if to_add: - dots.append(node.data) - - intersection = left <= coord <= right - self.search_list.append((node.data, to_add, intersection)) - - if node.left and left < coord: - dots.extend(self.region_search(node.left, not vertical)) - if node.right and coord < right: - dots.extend(self.region_search(node.right, not vertical)) - - return dots - - def dot_in_region(self, dot): - return ( - self.x_range[0] <= dot.x - and dot.x <= self.x_range[1] - and self.y_range[0] <= dot.y - and dot.y <= self.y_range[1] - ) - - -class ChainsBinTree(BinTree): - def make_tree(self, list, node): - mid = len(list) // 2 - if mid == 0: - return - - list_l = list[:mid] - list_r = list[-mid:] - left, right = list_l[mid // 2], list_r[mid // 2] - - node.left = NodeWithParent(left, node) - if node.data != right: - node.right = NodeWithParent(right, node) - - self.make_tree(list_l, node.left) - self.make_tree(list_r, node.right) - - @staticmethod - def _point_in_edge(edge, point): - return edge.v1[1] <= point.y and edge.v2[1] >= point.y - - @staticmethod - def _location_against_edge(point, edge): - return Point.direction(edge.v1.point, edge.v2.point, point) - - def search_point(self, point): - '''Returns a pair of chains the point is between''' - current_node = self.root - left_parent, right_parent = None, None - - while current_node: - edge = list( - filter(lambda e: ChainsBinTree._point_in_edge(e, point), current_node.data) - )[0] - location = ChainsBinTree._location_against_edge(point, edge) - - if location > 0: - if current_node.right is not None: - current_node = current_node.right - left_parent = current_node.parent - else: - return (current_node.data, right_parent.data) - - elif location < 0: - if current_node.left is not None: - current_node = current_node.left - right_parent = current_node.parent - else: - return (left_parent.data, current_node.data) - else: - return (current_node.data, None) diff --git a/models/bin_tree_node.py b/models/bin_tree_node.py deleted file mode 100644 index 888f7e6..0000000 --- a/models/bin_tree_node.py +++ /dev/null @@ -1,40 +0,0 @@ -class Node: - def __init__(self, data): - """By default Node has no children.""" - self.data = data - self.left = None - self.right = None - - def __eq__(self, other): - """Recursive equality.""" - return ( - self.data == other.data - and self.left == other.left - and self.right == other.right - ) - - -class NodeWithParent(Node): - def __init__(self, data, parent = None): - self.parent = parent - super().__init__(data) - - -class QuickhullData: - def __init__(self, points, h, hull_piece): - self.points = points - self.h = h - self.hull_piece = hull_piece - - def __eq__(self, other): - return ( - isinstance(other, QuickhullData) - and self.points == other.points - and self.h == other.h - and self.hull_piece == other.hull_piece - ) - - -class QuickhullNode(Node): - def __init__(self, points, h=None, hull_piece=None): - super().__init__(QuickhullData(points, h, hull_piece)) diff --git a/models/edge.py b/models/edge.py deleted file mode 100644 index 32c25ab..0000000 --- a/models/edge.py +++ /dev/null @@ -1,23 +0,0 @@ -class Edge: - def __init__(self, v1, v2, weight=0): - self.v1, self.v2, self.weight = v1, v2, weight - - def __hash__(self): - return hash(self.v1) + hash(self.v2) - - def __eq__(self, other): - return set((self.v1, self.v2)) == set((other.v1, other.v2)) - - def __str__(self): - return '({}, {})'.format(self.v1, self.v2) + f"weight = {repr(self.weight)}" - - -class OrientedEdge(Edge): - def __hash__(self): - return super().__hash__() + hash(self.weight) - - def __eq__(self, other: Edge): - return (self.v1, self.v2) == (other.v1, other.v2) - - def __repr__(self): - return f"{repr(self.v1)}->{repr(self.v2)}, weight = {repr(self.weight)}" diff --git a/models/graph.py b/models/graph.py deleted file mode 100644 index 2e55a71..0000000 --- a/models/graph.py +++ /dev/null @@ -1,44 +0,0 @@ -from .vertex import Vertex -from .edge import Edge, OrientedEdge - -class Graph: - edge_class = Edge - - def __init__(self): - self.vertices, self.edges = set(), set() - - def __str__(self): - """Return str for edges of graph.""" - return str(self.edges) - - def sorted_vertices(self, sort_key): - return sorted(self.vertices, key=sort_key) - - def add_vertex(self, v: Vertex): - self.vertices.add(v) - - def add_edge(self, v1: Vertex, v2: Vertex, weight=0): - e1 = self.edge_class(v1, v2, weight) - e2 = self.edge_class(v2, v1) - if (v1 in self.vertices and v2 in self.vertices - and e1 not in self.edges and e2 not in self.edges): - self.edges.add(e1) - - -class OrientedGraph(Graph): - edge_class = OrientedEdge - - def add_edge(self, v1: Vertex, v2: Vertex, weight=0): - if (v1 in self.vertices and v2 in self.vertices): - self.edges.add(self.edge_class(v1, v2, weight)) - - def is_regular(self): - ''' - Checks whether a graph is regular, i.e. each of its vertices - has both incoming and outcoming edge(s), - except for the starting (no incoming) and ending (no outcoming). - ''' - sorted_vertices = self.sorted_vertices(sort_key=lambda v: v.point.y)[1:-1] - regular_vertices = [e.v1 for e in self.edges] + [e.v2 for e in self.edges] - - return all(v in regular_vertices for v in sorted_vertices) diff --git a/models/hull.py b/models/hull.py deleted file mode 100644 index f366148..0000000 --- a/models/hull.py +++ /dev/null @@ -1,45 +0,0 @@ -from itertools import cycle, dropwhile, takewhile, chain -from .polygon import Polygon -from .point import Point -from .vector import Vector -from ..algo.graham import graham - - -class Hull(Polygon): - def __add__(self, other): - """Merge two hulls in one.""" - p1, p2, p3 = self.points[:3] - centroid = Point.centroid((p1, p2, p3)) - if other.contains_point(centroid): - points = list(self) + list(other) - else: - points = list(self) + list(other.get_arc(centroid)) - return list(graham(points))[-1] - - def reference_points(self, point): - v = Vector((0, 1)) - def key (end_point): - return v.signed_angle(Vector.from_two_points(point, end_point)) - return (min(self, key=key), max(self, key=key)) - - def get_arc(self, point): - point_cycle = cycle(self) - u, v = self.reference_points(point) - - def arc(start, end): - """Return arc of point_cycle with start and end exclusively.""" - return list( - chain( - takewhile( - lambda x: x != end, dropwhile(lambda x: x != start, point_cycle) - ), - (end,), - ) - ) - - arc1, arc2 = arc(u, v), arc(v, u) - - def key(arc): - return Polygon(list(arc)+[point]).area - - return max((arc1, arc2), key=key) diff --git a/models/line2d.py b/models/line2d.py deleted file mode 100644 index d73a69d..0000000 --- a/models/line2d.py +++ /dev/null @@ -1,22 +0,0 @@ -from .point import Point - - -class Line2D: - """A 2D line represented by the equation Ax + By + C = 0.""" - - def __init__(self, p1: Point, p2: Point): - """Construct line by two points.""" - self.p1 = p1 - self.p2 = p2 - - @property - def A(self): - return self.p1.y - self.p2.y - - @property - def B(self): - return self.p2.x - self.p1.x - - @property - def C(self): - return self.p1.x * self.p2.y - self.p2.x * self.p1.y diff --git a/models/point.py b/models/point.py deleted file mode 100644 index 5625a88..0000000 --- a/models/point.py +++ /dev/null @@ -1,112 +0,0 @@ -import math -from operator import add, sub -from functools import reduce -from .vector import Vector - - -class Point: - def __init__(self, *args): - """Make tuple of args.""" - self.coords = tuple(map(float, args)) - - def dominating(self, other): - '''True if each self coordinate is bigger than other''' - return reduce( - lambda a, b: a and b[0] >= b[1], zip(self.coords, other.coords), True - ) - - @property - def x(self): - return self.coords[0] - - @property - def y(self): - return self.coords[1] - - @property - def z(self): - return self.coords[2] - - @property - def dim(self): - return len(self.coords) - - def __getitem__(self, key): - """Wrap tuple-like behavior.""" - return self.coords[key] - - def __str__(self): - """Coords string representation.""" - return "(%s, %s)" % (self.x, self.y) - - def __eq__(self, other): - """Compares point coords.""" - return self.coords == other.coords - - def __lt__(self, other): - """Compares point coords.""" - return self.coords < other.coords - - def __add__(self, other): - """To delete.""" - return Point(*list(map(add, self.coords, other.coords))) - - def __sub__(self, other): - """To delete.""" - return Point(*list(map(sub, self.coords, other.coords))) - - def dist_to_point(self, other): - '''Euclidean distance to point''' - s = sum([(a - b) ** 2 for a, b in zip(self.coords, other.coords)]) - return math.sqrt(s) - - def dist_to_line(self, line): - '''Euclidean distance to the 2D line''' - return ( - abs(line.A * self.x + line.B * self.y + line.C) / - math.sqrt(line.A ** 2 + line.B ** 2) - ) - - def angle_with(self, point1, point2): - '''Angle point1-self-point2 in [-pi, pi]''' - v1 = Vector.from_two_points(self, point1) - v2 = Vector.from_two_points(self, point2) - v1.normalize() - v2.normalize() - - return math.acos(v1 * v2 / (v1.euclidean_norm * v2.euclidean_norm)) - - def polar_angle_with(self, other): - '''Polar angle between self and other with other as origin''' - return math.atan2(self.y - other.y, self.x - other.x) - - def ccw_polar_angle_with(self, other): - '''Non-negative polar angle between self and other with other as origin''' - angle = self.polar_angle_with(other) - return angle if angle >= 0 else 2 * math.pi + angle - - - def __hash__(self): - '''Hash all the point representation''' - return hash(self.coords) - - def __repr__(self): - '''Representation for debugging.''' - return str(self) - - @staticmethod - def direction(point1, point2, point3): - '''Numeric description of point positions. - - < 0 if point3 is at the left of vector point1->point2; - > 0 if point3 is at the right of vector point1->point2; - = 0 if point3 is at the vector point1->point2. - ''' - v1 = Vector.from_two_points(point1, point3) - v2 = Vector.from_two_points(point1, point2) - return v1.cross_product_with(v2) - - @staticmethod - def centroid(point_iter): - '''Coordinate-wise mean of points iterable''' - return Point(*(sum(coord) / len(coord) for coord in zip(*point_iter))) diff --git a/models/polygon.py b/models/polygon.py deleted file mode 100644 index f55b30c..0000000 --- a/models/polygon.py +++ /dev/null @@ -1,46 +0,0 @@ -import math -from functools import reduce -from .point import Point -from .vector import Vector -from .triangle import Triangle - - -class Polygon: - def __init__(self, points): - """Make polygon by point list.""" - self.points = points - - def __getitem__(self, key): - """Wraps tuple-like behavior.""" - return self.points[key] - - @property - def point_pairs(self): - def cyclic_offset(li, n): - return li[-n:] + li[:-n] - - return zip(self.points, cyclic_offset(self.points, 1)) - - def contains_point(self, point): - pairs = self.point_pairs - - def angle(center, p1, p2): - v1 = Vector.from_two_points(center, p1) - v2 = Vector.from_two_points(center, p2) - return v1.signed_angle(v2) - - total_angle = reduce( - lambda accum, a: accum + angle(point, a[0], a[1]), pairs, 0 - ) - return total_angle > math.pi - - @property - def area(self): - a, b, c = self.points[:3] - p = Point.centroid((a, b, c)) - pairs = self.point_pairs - - def accumulate_triangle_area(area_sum, pair): - return area_sum + Triangle(p, pair[0], pair[1]).area - - return reduce(accumulate_triangle_area, pairs, 0) diff --git a/models/region_tree.py b/models/region_tree.py deleted file mode 100644 index 06c1793..0000000 --- a/models/region_tree.py +++ /dev/null @@ -1,66 +0,0 @@ -from itertools import groupby, chain -from .bin_tree_node import Node -from .bin_tree import BinTree - - -class RegionTree(BinTree): - def __init__(self, points): - self.__y_sort_flat = lambda l: sorted(chain.from_iterable(l), key=lambda u: u.y) - self.x_ordered = sorted(points) - self.y_ordered = sorted(self.x_ordered, key=lambda u: u.y) - self.projections = [list(g) for _, g in groupby(self.x_ordered, key=lambda u: u.x)] - interval = [1, len(self.projections)] - super().__init__(Node([interval, self.__y_sort_flat(self.projections)])) - self.__build(self.root) - - def __build(self, node: Node): - start, end = node.data[0] - mid = (end + start) // 2 - if (end - start) == 1: - return - l_int, r_int = [start, mid], [mid, end] - l_list = self.__y_sort_flat(self.projections[start - 1:mid]) - r_list = self.__y_sort_flat(self.projections[mid - 1:end]) - if l_list: - node.left = Node([l_int, l_list]) - self.__build(node.left) - if r_list: - node.right = Node([r_int, r_list]) - self.__build(node.right) - - def region_search(self, x_range, y_range): - x_norm = self.region_normalization(self.projections, x_range) - if not x_norm: - return [] - nodes = RegionTree.primary_search(self.root, x_norm) - res = set(chain.from_iterable(self.secondary_search(node.data[1], y_range) for node in nodes)) - return res - - @staticmethod - def region_normalization(projections, x_range): - enum = list(enumerate([ls[0] for ls in projections])) - l_bound = next((x for x, val in enum if val.x >= x_range[0]), None) - r_bound = next((x for x, val in reversed(enum) if val.x <= x_range[1]), None) - return [l_bound + 1, r_bound + 1] if l_bound is not None and r_bound is not None else [] - - @staticmethod - def primary_search(node: Node, interval): - begin, end = node.data[0] - if begin == interval[0] and end == interval[1]: - return [node] - mid = (end + begin) // 2 - nodes = [] - if node.left and interval[0] < mid: - new_interval = [interval[0], mid if interval[1] > mid else interval[1]] - nodes.extend(RegionTree.primary_search(node.left, new_interval)) - if node.right and interval[1] > mid: - new_interval = [mid if interval[0] < mid else interval[0], interval[1]] - nodes.extend(RegionTree.primary_search(node.right, new_interval)) - return nodes - - @staticmethod - def secondary_search(ls, interval): - enum = list(enumerate(ls)) - l_bound = next((x for x, val in enum if val.y >= interval[0]), None) - r_bound = next((x for x, val in reversed(enum) if val.y <= interval[1]), None) - return ls[l_bound:r_bound + 1] if l_bound is not None and r_bound is not None else [] diff --git a/models/triangle.py b/models/triangle.py deleted file mode 100644 index 803c64e..0000000 --- a/models/triangle.py +++ /dev/null @@ -1,21 +0,0 @@ -import math - - -class Triangle: - def __init__(self, A, B, C): - """Make triangle from 3 objects.""" - self.A, self.B, self.C = A, B, C - - @property - def sides(self): - return ( - p1.dist_to_point(p2) - for p1, p2 in zip((self.C, self.A, self.B), (self.B, self.C, self.A)) - ) - - @property - def area(self): - """Heron`s formula.""" - p = sum(self.sides) / 2 - A, B, C = self.sides - return math.sqrt(p * (p - A) * (p - B) * (p - C)) \ No newline at end of file diff --git a/models/vector.py b/models/vector.py deleted file mode 100644 index 52c20ca..0000000 --- a/models/vector.py +++ /dev/null @@ -1,59 +0,0 @@ -import math - - -class Vector: - def __init__(self, coords): - """Make vector from coords iterable.""" - self.coords = coords - - def __len__(self): - """Dimension of vector instance.""" - return len(self.coords) - - @property - def x(self): - return self.coords[0] - - @property - def y(self): - return self.coords[1] - - @property - def z(self): - return self.coords[2] - - @property - def euclidean_norm(self): - return math.sqrt(sum((i ** 2 for i in self.coords))) - - def __mul__(self, other): - """Scalar vector multiplication.""" - return sum((i * j for i, j in zip(self.coords, other.coords))) - - def __getitem__(self, key): - return self.coords[key] - - def angle(self, other): - if len(self) == len(other): - return math.acos( - (self * other) / (self.euclidean_norm * other.euclidean_norm) - ) - - def signed_angle(self, other): - def abs_vect_mul_2d(v1, v2): - return v1[0] * v2[1] - v1[1] * v2[0] - - return math.asin( - abs_vect_mul_2d(self, other) - / (self.euclidean_norm * other.euclidean_norm) - ) - - @staticmethod - def from_two_points(p1, p2): - return Vector((p2 - p1).coords) - - def normalize(self): - self.coords = tuple(x / self.euclidean_norm for x in self.coords) - - def cross_product_with(self, other): - return self.x * other.y - other.x * self.y diff --git a/models/vertex.py b/models/vertex.py deleted file mode 100644 index 2aa9953..0000000 --- a/models/vertex.py +++ /dev/null @@ -1,18 +0,0 @@ -class Vertex: - def __init__(self, point): - self.point = point - - def __hash__(self): - return hash(self.point) - - def __eq__(self, other): - return self.point == other.point - - def __getitem__(self, x): - return self.point.coords[x] - - def __str__(self): - return str(self.point) - - def __repr__(self): - return str(self) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..fa7093a --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools>=42"] +build-backend = "setuptools.build_meta" \ No newline at end of file diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..7f6c9d4 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,19 @@ +[metadata] +name = CGLib +version = 1.0.2 +author = artandfi&Chmele&MaksymRud&DeerInBlack +author_email = artyom.fisunenko@gmail.com +description = Computational geometry algorithms library +url = https://github.com/OGKG/CGLib +classifiers = + Programming Language :: Python :: 3 + Operating System :: OS Independent + +[options] +package_dir = + = src +packages = find: +python_requires = >=3.6 + +[options.packages.find] +where = src \ No newline at end of file diff --git a/src/CGLib b/src/CGLib new file mode 160000 index 0000000..989a8f6 --- /dev/null +++ b/src/CGLib @@ -0,0 +1 @@ +Subproject commit 989a8f6eb05ccbf992d926873fe64b2454fa625f diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/test_algorithms.py b/tests/test_algorithms.py deleted file mode 100644 index 478c00b..0000000 --- a/tests/test_algorithms.py +++ /dev/null @@ -1,625 +0,0 @@ -import math -from copy import deepcopy -from unittest import TestCase -from collections import OrderedDict - - -from ..models.point import Point -from ..models.vertex import Vertex -from ..models.edge import Edge, OrientedEdge -from ..models.graph import Graph, OrientedGraph -from ..models.bin_tree_node import Node, QuickhullNode, NodeWithParent -from ..models.bin_tree import BinTree, ChainsBinTree, KdTree - - -from ..algo.stripe_method import stripe -from ..algo.kd_tree_method import kd_tree -from ..algo.jarvis import jarvis -from ..algo.graham import graham -from ..algo.quickhull import quickhull -from ..algo.loci import Loci -from ..algo.chain_method import chain_method -from ..algo.dc_closest_points import closest_points -from ..algo.region_tree_method import region_tree_method - - -class TestAlgorithms(TestCase): - """algorithm tests.""" - - def test_stripe(self): - p1 = Vertex(Point(7, 0)) - p2 = Vertex(Point(2, 2.5)) - p3 = Vertex(Point(12, 3)) - p4 = Vertex(Point(8, 5)) - p5 = Vertex(Point(0, 7)) - p6 = Vertex(Point(13, 8)) - p7 = Vertex(Point(6, 11)) - - g = Graph() - - g.add_vertex(p1) - g.add_vertex(p2) - g.add_vertex(p3) - g.add_vertex(p4) - g.add_vertex(p5) - g.add_vertex(p6) - g.add_vertex(p7) - - g.add_edge(p1, p2) - g.add_edge(p1, p3) - g.add_edge(p2, p3) - g.add_edge(p7, p6) - g.add_edge(p3, p6) - g.add_edge(p4, p6) - g.add_edge(p4, p5) - g.add_edge(p4, p7) - g.add_edge(p5, p7) - g.add_edge(p2, p5) - - dot = Point(11.5, 5.5) - - ans = list(stripe(g, dot)) - self.assertEqual( - ans[0], - [ - (-math.inf, 0.0), - (0.0, 2.5), - (2.5, 3.0), - (3.0, 5.0), - (5.0, 7.0), - (7.0, 8.0), - (8.0, 11.0), - (11.0, math.inf), - ], - ) - - self.assertTrue( - TestAlgorithms.fragmentation_eq( - ans[1], - { - (-math.inf, 0.0): [], - (0.0, 2.5): [Edge(p1, p2), Edge(p1, p3)], - (2.5, 3.0): [Edge(p1, p3), Edge(p2, p3), Edge(p2, p5)], - (3.0, 5.0): [Edge(p2, p5), Edge(p3, p6)], - (5.0, 7.0): [ - Edge(p2, p5), - Edge(p4, p5), - Edge(p4, p7), - Edge(p4, p6), - Edge(p3, p6), - ], - (7.0, 8.0): [ - Edge(p5, p7), - Edge(p4, p7), - Edge(p4, p6), - Edge(p3, p6), - ], - (8.0, 11.0): [Edge(p5, p7), Edge(p4, p7), Edge(p7, p6)], - (11.0, math.inf): [], - }, - ) - ) - - self.assertEqual(ans[2], (5.0, 7.0)) - self.assertEqual(ans[3], [Edge(p4, p6), Edge(p3, p6)]) - - @staticmethod - def fragmentation_eq(f1, f2): - for i in f1: - for item in f1[i]: - if item not in f2[i]: - return False - for i in f2: - for item in f2[i]: - if item not in f1[i]: - return False - return True - - def test_jarvis1(self): - pts = [ - Point(1, 4), - Point(0, 0), - Point(3, 3), - Point(3, 1), - Point(7, 0), - Point(5, 5), - Point(5, 2), - Point(9, 6), - ] - hull = [Point(0, 0), Point(1, 4), Point(9, 6), Point(7, 0)] - ans = jarvis(pts) - self.assertEqual(ans, hull) - - def test_jarvis2(self): - pts = [Point(3, 3), Point(1, 1), Point(5, 0)] - hull = [Point(1, 1), Point(3, 3), Point(5, 0)] - ans = jarvis(pts) - self.assertEqual(ans, hull) - - def test_kd_tree(self): - pts = [ - Point(0, 9), - Point(2, 3), - Point(3, 6), - Point(5, 8), - Point(6, 1), - Point(8, 13), - Point(10, 2), - Point(12, 4), - Point(14, 11), - Point(15, 5), - Point(17, 10) - ] - rx = [3, 14] - ry = [0, 8] - - ordered_x = pts - ordered_y = [ - Point(6, 1), - Point(10, 2), - Point(2, 3), - Point(12, 4), - Point(15, 5), - Point(3, 6), - Point(5, 8), - Point(0, 9), - Point(17, 10), - Point(14, 11), - Point(8, 13) - ] - - tree = KdTree(Node(Point(8, 13)), [], []) - tree.root.left = Node(Point(3, 6)) - tree.root.left.left = Node(Point(6, 1)) - tree.root.left.left.left = Node(Point(2, 3)) - tree.root.left.right = Node(Point(5, 8)) - tree.root.left.right.left = Node(Point(0, 9)) - - tree.root.right = Node(Point(15, 5)) - tree.root.right.left = Node(Point(12, 4)) - tree.root.right.left.left = Node(Point(10, 2)) - tree.root.right.right = Node(Point(17, 10)) - tree.root.right.right.left = Node(Point(14, 11)) - - partition = [ - (Point(8, 13), True), - (Point(3, 6), False), - (Point(6, 1), True), - (Point(2, 3), False), - (Point(5, 8), True), - (Point(0, 9), False), - (Point(15, 5), False), - (Point(12, 4), True), - (Point(10, 2), False), - (Point(17, 10), True), - (Point(14, 11), False) - ] - - search_list = [ - (Point(8, 13), False, True), - (Point(3, 6), True, True), - (Point(6, 1), True, True), - (Point(2, 3), False, True), - (Point(5, 8), True, True), - (Point(0, 9), False, False), - (Point(15, 5), False, True), - (Point(12, 4), True, True), - (Point(10, 2), True, True), - (Point(17, 10), False, False), - (Point(14, 11), False, False) - ] - - result = [ - Point(3, 6), - Point(5, 8), - Point(6, 1), - Point(10, 2), - Point(12, 4), - ] - - ans = kd_tree(pts, rx, ry) - - self.assertEqual((ordered_x, ordered_y), next(ans)) - self.assertEqual(partition, next(ans)) - self.assertEqual(tree, next(ans)) - self.assertEqual(search_list, next(ans)) - self.assertEqual(result, sorted(next(ans))) - - def test_graham1(self): - pts = [Point(7, 0), Point(3, 3), Point(0, 0)] - centroid = Point(3.3333333333333335, 1.0) - ordered = [Point(7, 0), Point(3, 3), Point(0, 0)] - origin = Point(7, 0) - steps = [ - ([ordered[0], ordered[1], ordered[2]], True), - ([ordered[1], ordered[2], ordered[0]], True) - ] - hull = [Point(7, 0), Point(3, 3), Point(0, 0)] - ans = graham(pts) - - self.assertAlmostEqual(centroid, next(ans)) - self.assertEqual(ordered, next(ans)) - self.assertEqual(origin, next(ans)) - self.assertEqual(steps, next(ans)) - self.assertEqual(hull, next(ans)) - - def test_graham2(self): - pts = [ - Point(3, 10), - Point(6, 8), - Point(3, 5), - Point(2, 8), - Point(4, 8), - Point(5, 5), - Point(3, 3), - Point(7, 7), - Point(5, 0), - Point(0, 0), - Point(10, 3), - ] - centroid = Point(4.0, 7.666666666666667) - ordered = [ - Point(5, 0), - Point(5, 5), - Point(10, 3), - Point(7, 7), - Point(6, 8), - Point(4, 8), - Point(3, 10), - Point(2, 8), - Point(0, 0), - Point(3, 5), - Point(3, 3), - ] - origin = Point(5, 0) - steps = [ - ([ordered[0], ordered[1], ordered[2]], False), - ([ordered[0], ordered[2], ordered[3]], True), - ([ordered[2], ordered[3], ordered[4]], True), - ([ordered[3], ordered[4], ordered[5]], True), - ([ordered[4], ordered[5], ordered[6]], False), - ([ordered[3], ordered[4], ordered[6]], True), - ([ordered[4], ordered[6], ordered[7]], True), - ([ordered[6], ordered[7], ordered[8]], True), - ([ordered[7], ordered[8], ordered[9]], True), - ([ordered[8], ordered[9], ordered[10]], False), - ([ordered[7], ordered[8], ordered[10]], True), - ([ordered[8], ordered[10], ordered[0]], False), - ([ordered[7], ordered[8], ordered[0]], True) - ] - hull = [ - Point(5, 0), - Point(10, 3), - Point(7, 7), - Point(6, 8), - Point(3, 10), - Point(2, 8), - Point(0, 0) - ] - ans = graham(pts) - self.assertAlmostEqual(centroid, next(ans)) - self.assertEqual(ordered, next(ans)) - self.assertEqual(origin, next(ans)) - self.assertEqual(steps, next(ans)) - self.assertEqual(hull, next(ans)) - - def test_graham3(self): - pts = [ - Point(2, 8), - Point(5, 6), - Point(7, 8), - Point(8, 11), - Point(7, 5), - Point(10, 7), - Point(11, 5), - Point(8, 2), - Point(1, 3), - Point(5, 2), - ] - centroid = Point(4.666666666666667, 7.333333333333333) - ordered = [ - Point(8, 2), - Point(7, 5), - Point(11, 5), - Point(10, 7), - Point(7, 8), - Point(8, 11), - Point(2, 8), - Point(1, 3), - Point(5, 2), - Point(5, 6) - ] - origin = Point(8, 2) - steps = [ - ([ordered[0], ordered[1], ordered[2]], False), - ([ordered[0], ordered[2], ordered[3]], True), - ([ordered[2], ordered[3], ordered[4]], True), - ([ordered[3], ordered[4], ordered[5]], False), - ([ordered[2], ordered[3], ordered[5]], False), - ([ordered[0], ordered[2], ordered[5]], True), - ([ordered[2], ordered[5], ordered[6]], True), - ([ordered[5], ordered[6], ordered[7]], True), - ([ordered[6], ordered[7], ordered[8]], True), - ([ordered[7], ordered[8], ordered[9]], True), - ([ordered[8], ordered[9], ordered[0]], False), - ([ordered[7], ordered[8], ordered[0]], True) - ] - hull = [ - Point(8, 2), - Point(11, 5), - Point(8, 11), - Point(2, 8), - Point(1, 3), - Point(5, 2) - ] - ans = graham(pts) - self.assertAlmostEqual(centroid, next(ans)) - self.assertEqual(ordered, next(ans)) - self.assertEqual(origin, next(ans)) - self.assertEqual(steps, next(ans)) - self.assertEqual(hull, next(ans)) - - def test_quickhull1(self): - pts = [Point(3, 4), Point(0, 0), Point(7, 2)] - hull = [pts[1], pts[0], pts[2]] - tree = BinTree(QuickhullNode([pts[1], pts[0], pts[2]], hull_piece=hull)) - tree.root.left = QuickhullNode([pts[1], pts[0], pts[2]], h=pts[0], hull_piece=hull) - tree.root.right = QuickhullNode([pts[2], pts[1]], hull_piece=[pts[2], pts[1]]) - tree.root.left.left = QuickhullNode([pts[1], pts[0]], hull_piece=[pts[1], pts[0]]) - tree.root.left.right = QuickhullNode([pts[0], pts[2]], hull_piece=[pts[0], pts[2]]) - - ans = quickhull(pts) - lp, rp, s1, s2, _ = next(ans) - - self.assertEqual((pts[1], pts[2]), (lp, rp)) - self.assertEqual(([pts[1], pts[0], pts[2]], [pts[2], pts[1]]), (s1, s2)) - self.assertEqual(tree, next(ans)) - self.assertEqual(hull, next(ans)) - - def test_quickhull2(self): - pts = [ - Point(0, 6), - Point(8, 11), - Point(10, 4), - Point(7, 13), - Point(6, 3), - Point(3, 0), - Point(4, 2), - Point(12, 1), - Point(14, 10), - Point(5, 9), - Point(3, 11), - Point(1, 4), - ] - hull = [pts[0], pts[10], pts[3], pts[8], pts[7], pts[5]] - tree = BinTree( - QuickhullNode( - [ - pts[0], - pts[10], - pts[9], - pts[3], - pts[1], - pts[8], - pts[7], - pts[2], - pts[4], - pts[6], - pts[5], - pts[11], - ], - hull_piece=hull - ) - ) - - tree.root.left = QuickhullNode( - [pts[0], pts[10], pts[9], pts[3], pts[1], pts[8]], - h=pts[3], - hull_piece=[pts[0], pts[10], pts[3], pts[8]] - ) - tree.root.right = QuickhullNode( - [pts[8], pts[7], pts[2], pts[4], pts[6], pts[5], pts[11], pts[0]], - h=pts[7], - hull_piece=[pts[8], pts[7], pts[5], pts[0]] - ) - - tree.root.left.left = QuickhullNode([pts[0], pts[10], pts[3]], h=pts[10], hull_piece=[pts[0], pts[10], pts[3]]) - tree.root.left.right = QuickhullNode([pts[3], pts[8]], hull_piece=[pts[3], pts[8]]) - tree.root.left.left.left = QuickhullNode([pts[0], pts[10]], hull_piece=[pts[0], pts[10]]) - tree.root.left.left.right = QuickhullNode([pts[10], pts[3]], hull_piece=[pts[10], pts[3]]) - - tree.root.right.left = QuickhullNode([pts[8], pts[7]], hull_piece=[pts[8], pts[7]]) - tree.root.right.right = QuickhullNode( - [pts[7], pts[4], pts[6], pts[5], pts[11], pts[0]], - h=pts[5], - hull_piece=[pts[7], pts[5], pts[0]] - ) - tree.root.right.right.left = QuickhullNode([pts[7], pts[5]], hull_piece=[pts[7], pts[5]]) - tree.root.right.right.right = QuickhullNode([pts[5], pts[0]], hull_piece=[pts[5], pts[0]]) - - ans = quickhull(pts) - lp, rp, s1, s2, _ = next(ans) - - self.assertEqual((pts[0], pts[8]), (lp, rp)) - self.assertEqual((tree.root.left.data.points, tree.root.right.data.points), (s1, s2)) - self.assertEqual(tree, next(ans)) - self.assertEqual(hull, next(ans)) - - def test_loci(self): - l = Loci() - p1 = Point(1, 1) - p2 = Point(2, 1) - p3 = Point(2, 3) - p4 = Point(2, 2) - - l.append_points(p1, p2, p3, p4) - q = l.query(Point(2.5, 0.5)) - self.assertEqual(q, 0) - res = l.get_points_in_rect(((1.5, 2.5), (0.5, 3.5))) - res2 = l.get_points_in_rect(((0.5, 2.5), (0.5, 3.5))) - - self.assertEqual(res, 3) - self.assertEqual(res2, 4) - - p1 = Point(2, 1) - p2 = Point(1, 2) - p3 = Point(0, 3) - l = Loci() - l.append_points(p1, p2, p3) - res = l.get_points_in_rect(((0.5, 2.5), (0.5, 2.5))) - self.assertEqual(res, 2) - - def test_chain_method(self): - graph = OrientedGraph() - point = Point(4, 5) - v1 = Vertex(Point(4, 2)) - v2 = Vertex(Point(2, 4)) - v3 = Vertex(Point(6, 5)) - v4 = Vertex(Point(5, 7)) - - e1 = OrientedEdge(v1, v2, 1) - e2 = OrientedEdge(v1, v3, 1) - e3 = OrientedEdge(v2, v3, 1) - e4 = OrientedEdge(v2, v4, 1) - e5 = OrientedEdge(v3, v4, 1) - - graph.add_vertex(v1) - graph.add_vertex(v2) - graph.add_vertex(v3) - graph.add_vertex(v4) - - graph.add_edge(v1, v2, 1) - graph.add_edge(v1, v3, 1) - graph.add_edge(v2, v3, 1) - graph.add_edge(v2, v4, 1) - graph.add_edge(v3, v4, 1) - - ordered = [v1, v2, v3, v4] - - weight_table = OrderedDict( - { - v1: {"vin": [], "vout": [e1, e2], "win": 0, "wout": 2}, - v2: {"vin": [e1], "vout": [e4, e3], "win": 1, "wout": 2}, - v3: {"vin": [e3, e2], "vout": [e5], "win": 2, "wout": 1}, - v4: {"vin": [e4, e5], "vout": [], "win": 2, "wout": 0}, - } - ) - - e1_balanced = copy.deepcopy(e1) - e1_balanced.weight = 2 - e5_balanced = copy.deepcopy(e5) - e5_balanced.weight = 2 - weight_table_balanced = { - v1: {"vin": [], "vout": [e1_balanced, e2], "win": 0, "wout": 3}, - v2: {"vin": [e1_balanced], "vout": [e4, e3], "win": 2, "wout": 2}, - v3: {"vin": [e3, e2], "vout": [e5_balanced], "win": 2, "wout": 2}, - v4: {"vin": [e4, e5_balanced], "vout": [], "win": 3, "wout": 0}, - } - - e1_new = deepcopy(e1) - e1_new.weight = 0 - e2_new = deepcopy(e2) - e2_new.weight = 0 - e3_new = deepcopy(e3) - e3_new.weight = 0 - e4_new = deepcopy(e4) - e4_new.weight = 0 - e5_new = deepcopy(e5) - e5_new.weight = 0 - - chains = [[e1_new, e4_new], [e1_new, e3_new, e5_new], [e2_new, e5_new]] - - root = NodeWithParent(data=chains[1]) - tree = ChainsBinTree(root) - tree.root.left = NodeWithParent(data=chains[0], parent=root) - tree.root.right = NodeWithParent(data=chains[2], parent=root) - - point_between = (chains[0], chains[1]) - - ans = chain_method(graph, point) - self.assertEqual(ordered, next(ans)) - self.assertEqual(weight_table, next(ans)) - self.assertEqual(weight_table_balanced, next(ans)) - self.assertEqual(chains, next(ans)) - self.assertEqual(tree, next(ans)) - self.assertEqual(point_between, next(ans)) - - def test_closest_points(self): - points_test = [Point(3, 3), Point(6, 2), Point(5, 6), Point(7, 4), Point(2, 9)] - - close_pair_true = (Point(6, 2), Point(7, 4)) - - self.assertTupleEqual(closest_points(points_test), close_pair_true) - - def test_region_tree_method(self): - pts = [Point(1, 9), Point(7, 13), Point(3, 3), Point(1.5, 3), Point(5, 7), - Point(9, 8), Point(6, 9), Point(7, 5), Point(7, 12), Point(4, 11), Point(1, 5)] - x_range, y_range = [2.2, 7.7], [6.6, 11.11] - - pre = (sorted(pts), sorted(sorted(pts), key=lambda u: u.y)) - projections = [ - [Point(1, 5), Point(1, 9)], - [Point(1.5, 3)], - [Point(3, 3)], - [Point(4, 11)], - [Point(5, 7)], - [Point(6, 9)], - [Point(7, 5), Point(7, 12), Point(7, 13)], - [Point(9, 8)] - ] - - tree = BinTree(Node([[1, 8], [Point(1.5, 3), - Point(3, 3), - Point(1, 5), - Point(7, 5), - Point(5, 7), - Point(9, 8), - Point(1, 9), - Point(6, 9), - Point(4, 11), - Point(7, 12), - Point(7, 13)]])) - tree.root.left = Node([[1, 4], [Point(1.5, 3), - Point(3, 3), - Point(1, 5), - Point(1, 9), - Point(4, 11)]]) - tree.root.left.left = Node([[1, 2], [Point(1.5, 3), Point(1, 5), Point(1, 9)]]) - tree.root.left.right = Node([[2, 4], [Point(1.5, 3), Point(3, 3), Point(4, 11)]]) - tree.root.left.right.left = Node([[2, 3], [Point(1.5, 3), Point(3, 3)]]) - tree.root.left.right.right = Node([[3, 4], [Point(3, 3), Point(4, 11)]]) - - tree.root.right = Node([[4, 8], [Point(7, 5), - Point(5, 7), - Point(9, 8), - Point(6, 9), - Point(4, 11), - Point(7, 12), - Point(7, 13)]]) - tree.root.right.left = Node([[4, 6], [Point(5, 7), Point(6, 9), Point(4, 11)]]) - tree.root.right.left.left = Node([[4, 5], [Point(5, 7), Point(4, 11)]]) - tree.root.right.left.right = Node([[5, 6], [Point(5, 7), Point(6, 9)]]) - tree.root.right.right = Node([[6, 8], [Point(7, 5), - Point(9, 8), - Point(6, 9), - Point(7, 12), - Point(7, 13)]]) - tree.root.right.right.left = Node([[6, 7], [Point(7, 5), - Point(6, 9), - Point(7, 12), - Point(7, 13)]]) - tree.root.right.right.right = Node([[7, 8], [Point(7, 5), - Point(9, 8), - Point(7, 12), - Point(7, 13)]]) - - ps = [tree.root.left.right.right, tree.root.right.left, tree.root.right.right.left] - ss = [[Point(4, 11)], [Point(5, 7), Point(6, 9), Point(4, 11)], [Point(6, 9)]] - - ans = region_tree_method(pts, x_range, y_range) - self.assertEqual(pre, next(ans)) - self.assertEqual(projections, next(ans)) - self.assertEqual(tree, next(ans)) - self.assertEqual([3, 7], next(ans)) - self.assertEqual(ps, next(ans)) - self.assertEqual(ss, next(ans)) diff --git a/tests/test_models.py b/tests/test_models.py deleted file mode 100644 index 80da964..0000000 --- a/tests/test_models.py +++ /dev/null @@ -1,133 +0,0 @@ -import unittest -import math -from ..models.point import Point -from ..models.vertex import Vertex -from ..models.graph import Graph -from ..models.vector import Vector -from ..models.triangle import Triangle -from ..models.polygon import Polygon -from ..models.hull import Hull - - -class TestModels(unittest.TestCase): - """Test class for basic entities.""" - - def test_point_creation(self): - p1 = Point(1, 2) - p2 = Point(1, 2, 3.6) - self.assertEqual(p1.coords, (1, 2)) - self.assertEqual(p2.coords, (1, 2, 3.6)) - - def test_graph_vertex_add(self): - g = Graph() - v1 = Vertex(Point(1, 2)) - v2 = Vertex(Point(2, 1)) - - g.add_vertex(v1) - g.add_vertex(v2) - - g.add_edge(v1, v2) - g.add_edge(v2, v1) - - self.assertEqual(len(g.edges), 1) - - def test_point_domination(self): - a = Point(1, 2) - b = Point(3, 4) - c = Point(2, 2) - self.assertTrue(b.dominating(a)) - self.assertTrue(b.dominating(c)) - self.assertFalse(a.dominating(c)) - - def test_vectors(self): - a = Vector([1, 1]) - b = Vector([0, 12]) - self.assertAlmostEqual(a.angle(b), math.pi / 4) - - def test_polygon_in(self): - p1 = Point(1, 1) - p2 = Point(1, -1) - p3 = Point(-1, -1) - p4 = Point(-1, 1) - p = Polygon([p1, p2, p3, p4]) - - p0 = Point(0, 0) - pn = Point(1.1, 1) - self.assertEqual(p.contains_point(p0), True) - self.assertNotEqual(p.contains_point(pn), True) - - def test_point_centroid(self): - p1 = Point(1, 2, 3) - p2 = Point(1, 5, 6) - p3 = Point(1, 2, 3) - self.assertEqual(Point.centroid((p1, p2, p3)), Point(1, 3, 4)) - - p1 = Point(1, 2, 3) - p2 = Point(1, 5, 6) - p3 = Point(1, 2, 3) - p4 = Point(1, 2, 3) - self.assertEqual(Point.centroid((p1, p2, p3, p4)), Point(1, 2.75, 3.75)) - - def test_triangle_area(self): - p1 = Point(0, 1) - p2 = Point(0, 0) - p3 = Point(1, 0) - t = Triangle(p1, p2, p3) - self.assertAlmostEqual(t.area, 0.5) - - p1 = Point(-100, 0) - p2 = Point(0, 100) - p3 = Point(100, 0) - t = Triangle(p1, p2, p3) - self.assertAlmostEqual(t.area, 10000) - - def test_polygon_area(self): - p1 = Point(0, 0) - p2 = Point(0, 100) - p3 = Point(100, 100) - p4 = Point(100, 0) - p = Polygon((p1, p2, p3, p4)) - self.assertAlmostEqual(p.area, 10000) - self.assertAlmostEqual(p.area, 10000) - - def test_hull_sum1(self): - p1 = Point(2, 2) - p2 = Point(2, -2) - p3 = Point(-2, -2) - p4 = Point(-2, 2) - r1 = Polygon((p1, p2, p3, p4)) - p1 = Point(3, 0) - p2 = Point(0, -3) - p3 = Point(-3, 0) - p4 = Point(0, 3) - r2 = Polygon((p1, p2, p3, p4)) - - h = Hull(r1) + Hull(r2) - self.assertEqual( - h, - [ - Point(0, -3), - Point(2, -2), - Point(3, 0), - Point(2, 2), - Point(0, 3), - Point(-2, 2), - Point(-3, 0), - Point(-2, -2) - ] - ) - - def test_hull_sum2(self): - p1 = Point(2, 2) - p2 = Point(2, 0) - p3 = Point(0, 0) - p4 = Point(0, 2) - r1 = Polygon((p1, p2, p3, p4)) - p1 = Point(-2, -2) - p2 = Point(-2, 0) - p3 = Point(0, -1) - p4 = Point(0, -2) - r2 = Polygon((p1, p2, p3, p4)) - - h = Hull(r1) + Hull(r2) - self.assertEqual(h, [Point(0, -2), Point(2, 0), Point(2, 2), Point(0, 2), Point(-2, 0), Point(-2, -2)])