Advertisement
Guest User

Untitled

a guest
Sep 6th, 2022
63
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 6.26 KB | None | 0 0
  1. #
  2. # Smallest enclosing circle - Library (Python)
  3. #
  4. # Copyright (c) 2020 Project Nayuki
  5. # https://www.nayuki.io/page/smallest-enclosing-circle
  6. #
  7. # This program is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU Lesser General Public License as published by
  9. # the Free Software Foundation, either version 3 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # This program is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU Lesser General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU Lesser General Public License
  18. # along with this program (see COPYING.txt and COPYING.LESSER.txt).
  19. # If not, see <http://www.gnu.org/licenses/>.
  20. #
  21.  
  22. import math
  23. from numba.pycc import CC
  24.  
  25. cc = CC('smallest_circle')
  26. cc.verbose = True
  27.  
  28. # Data conventions: A point is a pair of floats (x, y). A circle is a triple of floats (center x, center y, radius).
  29.  
  30. # Returns the smallest circle that encloses all the given points. Runs in expected O(n) time, randomized.
  31. # Input: A sequence of pairs of floats or ints, e.g. [(0,5), (3.1,-2.7)].
  32. # Output: A triple of floats representing a circle.
  33. # Note: If 0 points are given, None is returned. If 1 point is given, a circle of radius 0 is returned.
  34. #
  35. # Initially: No boundary points known
  36.  
  37.  
  38. @cc.export('is_in_circle', 'b1(f8[:], f8[:])')
  39. def is_in_circle(c, p):
  40. if (c is not None) and (math.hypot(p[0] - c[0], p[1] - c[1]) <= c[2] * _MULTIPLICATIVE_EPSILON):
  41. res = True
  42. else:
  43. res = False
  44. return res
  45.  
  46.  
  47. @cc.export('make_circle', '(f8[:,:],)')
  48. def make_circle(points):
  49. # points : numpy.ndarray (3,2) float64
  50. # Convert to float and randomize order
  51. shuffled = points[np.random.permutation(len(points))]
  52.  
  53. # Progressively add points to circle or recompute circle
  54. first_pass = True
  55. c = (0,0,0)
  56. for (i, p) in enumerate(shuffled):
  57. if first_pass is True or not is_in_circle(c, p):
  58. first_pass = False
  59. c = _make_circle_one_point(shuffled[: i + 1], p)
  60. return c
  61.  
  62.  
  63. # One boundary point known
  64. @cc.export('make_circle_one_point', '(f8[:,:], f8[:])')
  65. def _make_circle_one_point(points, p):
  66. # points : numpy.ndarray (n,2) float64
  67. # p : numpy.ndarray (2,) float64
  68. c = (p[0], p[1], 0.0)
  69. for (i, q) in enumerate(points):
  70. if not is_in_circle(c, q):
  71. if c[2] == 0.0:
  72. c = make_diameter(p, q)
  73. else:
  74. c = _make_circle_two_points(points[: i + 1], p, q)
  75. return c
  76.  
  77.  
  78. # Two boundary points known
  79. @cc.export('make_circle_two_points', '(f8[:,:],f8[:],f8[:])')
  80. def _make_circle_two_points(points, p, q):
  81. # points : numpy.ndarray (n,2) float64
  82. # p : numpy.ndarray (2,) float64
  83. # q : numpy.ndarray (2,) float64
  84. circ = make_diameter(p, q)
  85. left = (0, 0, 0)
  86. left_valid = False
  87. right = (0, 0, 0)
  88. right_valid = False
  89. px, py = p
  90. qx, qy = q
  91.  
  92. # For each point not in the two-point circle
  93. for r in points:
  94. if is_in_circle(circ, r):
  95. continue
  96.  
  97. # Form a circumcircle and classify it on left or right side
  98. cross = _cross_product(px, py, qx, qy, r[0], r[1])
  99. c, c_valid = make_circumcircle(p, q, r)
  100. if c_valid is False:
  101. continue
  102. elif cross > 0.0 and (
  103. left_valid is False or
  104. _cross_product(px, py, qx, qy, c[0], c[1]) > _cross_product(px, py, qx, qy, left[0], left[1])):
  105. left = c
  106. left_valid = True
  107. elif cross < 0.0 and (right_valid is False or _cross_product(px, py, qx, qy, c[0], c[1]) < _cross_product(px, py, qx, qy, right[0], right[1])):
  108. right = c
  109. right_valid = True
  110. # Select which circle to return
  111. if left_valid is False and right_valid is False:
  112. return circ
  113. elif left_valid is False:
  114. return right
  115. elif right_valid is False:
  116. return left
  117. else:
  118. return left if (left[2] <= right[2]) else right
  119.  
  120.  
  121. @cc.export('make_diameter', '(f8[:],f8[:])')
  122. def make_diameter(a, b):
  123. # a : numpy.ndarray (2,) float64
  124. # b : numpy.ndarray (2,) float64
  125. cx = (a[0] + b[0]) / 2
  126. cy = (a[1] + b[1]) / 2
  127. r0 = math.hypot(cx - a[0], cy - a[1])
  128. r1 = math.hypot(cx - b[0], cy - b[1])
  129. return (cx, cy, max(r0, r1))
  130.  
  131.  
  132. @cc.export('make_circumcircle', '(f8[:], f8[:], f8[:])')
  133. def make_circumcircle(a, b, c):
  134. # Mathematical algorithm from Wikipedia: Circumscribed circle
  135. # a : numpy.ndarray (2,) float64
  136. # b : numpy.ndarray (2,) float64
  137. # c : numpy.ndarray (2,) float64
  138. ox = (min(a[0], b[0], c[0]) + max(a[0], b[0], c[0])) / 2
  139. oy = (min(a[1], b[1], c[1]) + max(a[1], b[1], c[1])) / 2
  140. ax = a[0] - ox
  141. ay = a[1] - oy
  142. bx = b[0] - ox
  143. by = b[1] - oy
  144. cx = c[0] - ox
  145. cy = c[1] - oy
  146. d = (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by)) * 2.0
  147. if d == 0.0:
  148. return (0,0,0), False
  149. x = ox + ((ax * ax + ay * ay) * (by - cy) + (bx * bx + by * by) * (cy - ay) + (cx * cx + cy * cy) * (ay - by)) / d
  150. y = oy + ((ax * ax + ay * ay) * (cx - bx) + (bx * bx + by * by) * (ax - cx) + (cx * cx + cy * cy) * (bx - ax)) / d
  151. ra = math.hypot(x - a[0], y - a[1])
  152. rb = math.hypot(x - b[0], y - b[1])
  153. rc = math.hypot(x - c[0], y - c[1])
  154. return (x, y, max(ra, rb, rc)), True
  155.  
  156.  
  157. _MULTIPLICATIVE_EPSILON = 1 + 1e-14
  158.  
  159.  
  160. # Returns twice the signed area of the triangle defined by (x0, y0), (x1, y1), (x2, y2).
  161. @cc.export('cross_product', 'f8(f8,f8,f8,f8,f8,f8)')
  162. def _cross_product(x0, y0, x1, y1, x2, y2):
  163. return (x1 - x0) * (y2 - y0) - (y1 - y0) * (x2 - x0)
  164.  
  165. if __name__ == '__main__':
  166. cc.compile()
  167.  
  168. import matplotlib.pyplot as plt
  169. import matplotlib.patches as patches
  170. import numpy as np
  171.  
  172. # Create 30 random points
  173. points = np.random.rand(30, 2)
  174.  
  175. # Compute the smallest enclosing circle
  176. c = make_circle(points)
  177.  
  178. # Plot the points and the enclosing circle
  179. fig, ax = plt.subplots()
  180. ax.set_aspect('equal')
  181. ax.add_patch(patches.Circle(c[:2], c[2], fill=False))
  182. ax.scatter(points[:, 0], points[:, 1])
  183. plt.show()
Tags: python
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement