m2m模型翻译
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2883 lines
80 KiB

6 months ago
  1. from sympy.core import Expr, S, oo, pi, sympify
  2. from sympy.core.evalf import N
  3. from sympy.core.sorting import default_sort_key, ordered
  4. from sympy.core.symbol import _symbol, Dummy, symbols, Symbol
  5. from sympy.functions.elementary.complexes import sign
  6. from sympy.functions.elementary.piecewise import Piecewise
  7. from sympy.functions.elementary.trigonometric import cos, sin, tan
  8. from .ellipse import Circle
  9. from .entity import GeometryEntity, GeometrySet
  10. from .exceptions import GeometryError
  11. from .line import Line, Segment, Ray
  12. from .point import Point
  13. from sympy.logic import And
  14. from sympy.matrices import Matrix
  15. from sympy.simplify.simplify import simplify
  16. from sympy.solvers.solvers import solve
  17. from sympy.utilities.iterables import has_dups, has_variety, uniq, rotate_left, least_rotation
  18. from sympy.utilities.misc import as_int, func_name
  19. from mpmath.libmp.libmpf import prec_to_dps
  20. import warnings
  21. class Polygon(GeometrySet):
  22. """A two-dimensional polygon.
  23. A simple polygon in space. Can be constructed from a sequence of points
  24. or from a center, radius, number of sides and rotation angle.
  25. Parameters
  26. ==========
  27. vertices : sequence of Points
  28. Optional parameters
  29. ==========
  30. n : If > 0, an n-sided RegularPolygon is created. See below.
  31. Default value is 0.
  32. Attributes
  33. ==========
  34. area
  35. angles
  36. perimeter
  37. vertices
  38. centroid
  39. sides
  40. Raises
  41. ======
  42. GeometryError
  43. If all parameters are not Points.
  44. See Also
  45. ========
  46. sympy.geometry.point.Point, sympy.geometry.line.Segment, Triangle
  47. Notes
  48. =====
  49. Polygons are treated as closed paths rather than 2D areas so
  50. some calculations can be be negative or positive (e.g., area)
  51. based on the orientation of the points.
  52. Any consecutive identical points are reduced to a single point
  53. and any points collinear and between two points will be removed
  54. unless they are needed to define an explicit intersection (see examples).
  55. A Triangle, Segment or Point will be returned when there are 3 or
  56. fewer points provided.
  57. Examples
  58. ========
  59. >>> from sympy import Polygon, pi
  60. >>> p1, p2, p3, p4, p5 = [(0, 0), (1, 0), (5, 1), (0, 1), (3, 0)]
  61. >>> Polygon(p1, p2, p3, p4)
  62. Polygon(Point2D(0, 0), Point2D(1, 0), Point2D(5, 1), Point2D(0, 1))
  63. >>> Polygon(p1, p2)
  64. Segment2D(Point2D(0, 0), Point2D(1, 0))
  65. >>> Polygon(p1, p2, p5)
  66. Segment2D(Point2D(0, 0), Point2D(3, 0))
  67. The area of a polygon is calculated as positive when vertices are
  68. traversed in a ccw direction. When the sides of a polygon cross the
  69. area will have positive and negative contributions. The following
  70. defines a Z shape where the bottom right connects back to the top
  71. left.
  72. >>> Polygon((0, 2), (2, 2), (0, 0), (2, 0)).area
  73. 0
  74. When the keyword `n` is used to define the number of sides of the
  75. Polygon then a RegularPolygon is created and the other arguments are
  76. interpreted as center, radius and rotation. The unrotated RegularPolygon
  77. will always have a vertex at Point(r, 0) where `r` is the radius of the
  78. circle that circumscribes the RegularPolygon. Its method `spin` can be
  79. used to increment that angle.
  80. >>> p = Polygon((0,0), 1, n=3)
  81. >>> p
  82. RegularPolygon(Point2D(0, 0), 1, 3, 0)
  83. >>> p.vertices[0]
  84. Point2D(1, 0)
  85. >>> p.args[0]
  86. Point2D(0, 0)
  87. >>> p.spin(pi/2)
  88. >>> p.vertices[0]
  89. Point2D(0, 1)
  90. """
  91. def __new__(cls, *args, n = 0, **kwargs):
  92. if n:
  93. args = list(args)
  94. # return a virtual polygon with n sides
  95. if len(args) == 2: # center, radius
  96. args.append(n)
  97. elif len(args) == 3: # center, radius, rotation
  98. args.insert(2, n)
  99. return RegularPolygon(*args, **kwargs)
  100. vertices = [Point(a, dim=2, **kwargs) for a in args]
  101. # remove consecutive duplicates
  102. nodup = []
  103. for p in vertices:
  104. if nodup and p == nodup[-1]:
  105. continue
  106. nodup.append(p)
  107. if len(nodup) > 1 and nodup[-1] == nodup[0]:
  108. nodup.pop() # last point was same as first
  109. # remove collinear points
  110. i = -3
  111. while i < len(nodup) - 3 and len(nodup) > 2:
  112. a, b, c = nodup[i], nodup[i + 1], nodup[i + 2]
  113. if Point.is_collinear(a, b, c):
  114. nodup.pop(i + 1)
  115. if a == c:
  116. nodup.pop(i)
  117. else:
  118. i += 1
  119. vertices = list(nodup)
  120. if len(vertices) > 3:
  121. return GeometryEntity.__new__(cls, *vertices, **kwargs)
  122. elif len(vertices) == 3:
  123. return Triangle(*vertices, **kwargs)
  124. elif len(vertices) == 2:
  125. return Segment(*vertices, **kwargs)
  126. else:
  127. return Point(*vertices, **kwargs)
  128. @property
  129. def area(self):
  130. """
  131. The area of the polygon.
  132. Notes
  133. =====
  134. The area calculation can be positive or negative based on the
  135. orientation of the points. If any side of the polygon crosses
  136. any other side, there will be areas having opposite signs.
  137. See Also
  138. ========
  139. sympy.geometry.ellipse.Ellipse.area
  140. Examples
  141. ========
  142. >>> from sympy import Point, Polygon
  143. >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)])
  144. >>> poly = Polygon(p1, p2, p3, p4)
  145. >>> poly.area
  146. 3
  147. In the Z shaped polygon (with the lower right connecting back
  148. to the upper left) the areas cancel out:
  149. >>> Z = Polygon((0, 1), (1, 1), (0, 0), (1, 0))
  150. >>> Z.area
  151. 0
  152. In the M shaped polygon, areas do not cancel because no side
  153. crosses any other (though there is a point of contact).
  154. >>> M = Polygon((0, 0), (0, 1), (2, 0), (3, 1), (3, 0))
  155. >>> M.area
  156. -3/2
  157. """
  158. area = 0
  159. args = self.args
  160. for i in range(len(args)):
  161. x1, y1 = args[i - 1].args
  162. x2, y2 = args[i].args
  163. area += x1*y2 - x2*y1
  164. return simplify(area) / 2
  165. @staticmethod
  166. def _isright(a, b, c):
  167. """Return True/False for cw/ccw orientation.
  168. Examples
  169. ========
  170. >>> from sympy import Point, Polygon
  171. >>> a, b, c = [Point(i) for i in [(0, 0), (1, 1), (1, 0)]]
  172. >>> Polygon._isright(a, b, c)
  173. True
  174. >>> Polygon._isright(a, c, b)
  175. False
  176. """
  177. ba = b - a
  178. ca = c - a
  179. t_area = simplify(ba.x*ca.y - ca.x*ba.y)
  180. res = t_area.is_nonpositive
  181. if res is None:
  182. raise ValueError("Can't determine orientation")
  183. return res
  184. @property
  185. def angles(self):
  186. """The internal angle at each vertex.
  187. Returns
  188. =======
  189. angles : dict
  190. A dictionary where each key is a vertex and each value is the
  191. internal angle at that vertex. The vertices are represented as
  192. Points.
  193. See Also
  194. ========
  195. sympy.geometry.point.Point, sympy.geometry.line.LinearEntity.angle_between
  196. Examples
  197. ========
  198. >>> from sympy import Point, Polygon
  199. >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)])
  200. >>> poly = Polygon(p1, p2, p3, p4)
  201. >>> poly.angles[p1]
  202. pi/2
  203. >>> poly.angles[p2]
  204. acos(-4*sqrt(17)/17)
  205. """
  206. # Determine orientation of points
  207. args = self.vertices
  208. cw = self._isright(args[-1], args[0], args[1])
  209. ret = {}
  210. for i in range(len(args)):
  211. a, b, c = args[i - 2], args[i - 1], args[i]
  212. ang = Ray(b, a).angle_between(Ray(b, c))
  213. if cw ^ self._isright(a, b, c):
  214. ret[b] = 2*S.Pi - ang
  215. else:
  216. ret[b] = ang
  217. return ret
  218. @property
  219. def ambient_dimension(self):
  220. return self.vertices[0].ambient_dimension
  221. @property
  222. def perimeter(self):
  223. """The perimeter of the polygon.
  224. Returns
  225. =======
  226. perimeter : number or Basic instance
  227. See Also
  228. ========
  229. sympy.geometry.line.Segment.length
  230. Examples
  231. ========
  232. >>> from sympy import Point, Polygon
  233. >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)])
  234. >>> poly = Polygon(p1, p2, p3, p4)
  235. >>> poly.perimeter
  236. sqrt(17) + 7
  237. """
  238. p = 0
  239. args = self.vertices
  240. for i in range(len(args)):
  241. p += args[i - 1].distance(args[i])
  242. return simplify(p)
  243. @property
  244. def vertices(self):
  245. """The vertices of the polygon.
  246. Returns
  247. =======
  248. vertices : list of Points
  249. Notes
  250. =====
  251. When iterating over the vertices, it is more efficient to index self
  252. rather than to request the vertices and index them. Only use the
  253. vertices when you want to process all of them at once. This is even
  254. more important with RegularPolygons that calculate each vertex.
  255. See Also
  256. ========
  257. sympy.geometry.point.Point
  258. Examples
  259. ========
  260. >>> from sympy import Point, Polygon
  261. >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)])
  262. >>> poly = Polygon(p1, p2, p3, p4)
  263. >>> poly.vertices
  264. [Point2D(0, 0), Point2D(1, 0), Point2D(5, 1), Point2D(0, 1)]
  265. >>> poly.vertices[0]
  266. Point2D(0, 0)
  267. """
  268. return list(self.args)
  269. @property
  270. def centroid(self):
  271. """The centroid of the polygon.
  272. Returns
  273. =======
  274. centroid : Point
  275. See Also
  276. ========
  277. sympy.geometry.point.Point, sympy.geometry.util.centroid
  278. Examples
  279. ========
  280. >>> from sympy import Point, Polygon
  281. >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)])
  282. >>> poly = Polygon(p1, p2, p3, p4)
  283. >>> poly.centroid
  284. Point2D(31/18, 11/18)
  285. """
  286. A = 1/(6*self.area)
  287. cx, cy = 0, 0
  288. args = self.args
  289. for i in range(len(args)):
  290. x1, y1 = args[i - 1].args
  291. x2, y2 = args[i].args
  292. v = x1*y2 - x2*y1
  293. cx += v*(x1 + x2)
  294. cy += v*(y1 + y2)
  295. return Point(simplify(A*cx), simplify(A*cy))
  296. def second_moment_of_area(self, point=None):
  297. """Returns the second moment and product moment of area of a two dimensional polygon.
  298. Parameters
  299. ==========
  300. point : Point, two-tuple of sympifyable objects, or None(default=None)
  301. point is the point about which second moment of area is to be found.
  302. If "point=None" it will be calculated about the axis passing through the
  303. centroid of the polygon.
  304. Returns
  305. =======
  306. I_xx, I_yy, I_xy : number or SymPy expression
  307. I_xx, I_yy are second moment of area of a two dimensional polygon.
  308. I_xy is product moment of area of a two dimensional polygon.
  309. Examples
  310. ========
  311. >>> from sympy import Polygon, symbols
  312. >>> a, b = symbols('a, b')
  313. >>> p1, p2, p3, p4, p5 = [(0, 0), (a, 0), (a, b), (0, b), (a/3, b/3)]
  314. >>> rectangle = Polygon(p1, p2, p3, p4)
  315. >>> rectangle.second_moment_of_area()
  316. (a*b**3/12, a**3*b/12, 0)
  317. >>> rectangle.second_moment_of_area(p5)
  318. (a*b**3/9, a**3*b/9, a**2*b**2/36)
  319. References
  320. ==========
  321. .. [1] https://en.wikipedia.org/wiki/Second_moment_of_area
  322. """
  323. I_xx, I_yy, I_xy = 0, 0, 0
  324. args = self.vertices
  325. for i in range(len(args)):
  326. x1, y1 = args[i-1].args
  327. x2, y2 = args[i].args
  328. v = x1*y2 - x2*y1
  329. I_xx += (y1**2 + y1*y2 + y2**2)*v
  330. I_yy += (x1**2 + x1*x2 + x2**2)*v
  331. I_xy += (x1*y2 + 2*x1*y1 + 2*x2*y2 + x2*y1)*v
  332. A = self.area
  333. c_x = self.centroid[0]
  334. c_y = self.centroid[1]
  335. # parallel axis theorem
  336. I_xx_c = (I_xx/12) - (A*(c_y**2))
  337. I_yy_c = (I_yy/12) - (A*(c_x**2))
  338. I_xy_c = (I_xy/24) - (A*(c_x*c_y))
  339. if point is None:
  340. return I_xx_c, I_yy_c, I_xy_c
  341. I_xx = (I_xx_c + A*((point[1]-c_y)**2))
  342. I_yy = (I_yy_c + A*((point[0]-c_x)**2))
  343. I_xy = (I_xy_c + A*((point[0]-c_x)*(point[1]-c_y)))
  344. return I_xx, I_yy, I_xy
  345. def first_moment_of_area(self, point=None):
  346. """
  347. Returns the first moment of area of a two-dimensional polygon with
  348. respect to a certain point of interest.
  349. First moment of area is a measure of the distribution of the area
  350. of a polygon in relation to an axis. The first moment of area of
  351. the entire polygon about its own centroid is always zero. Therefore,
  352. here it is calculated for an area, above or below a certain point
  353. of interest, that makes up a smaller portion of the polygon. This
  354. area is bounded by the point of interest and the extreme end
  355. (top or bottom) of the polygon. The first moment for this area is
  356. is then determined about the centroidal axis of the initial polygon.
  357. References
  358. ==========
  359. .. [1] https://skyciv.com/docs/tutorials/section-tutorials/calculating-the-statical-or-first-moment-of-area-of-beam-sections/?cc=BMD
  360. .. [2] https://mechanicalc.com/reference/cross-sections
  361. Parameters
  362. ==========
  363. point: Point, two-tuple of sympifyable objects, or None (default=None)
  364. point is the point above or below which the area of interest lies
  365. If ``point=None`` then the centroid acts as the point of interest.
  366. Returns
  367. =======
  368. Q_x, Q_y: number or SymPy expressions
  369. Q_x is the first moment of area about the x-axis
  370. Q_y is the first moment of area about the y-axis
  371. A negative sign indicates that the section modulus is
  372. determined for a section below (or left of) the centroidal axis
  373. Examples
  374. ========
  375. >>> from sympy import Point, Polygon
  376. >>> a, b = 50, 10
  377. >>> p1, p2, p3, p4 = [(0, b), (0, 0), (a, 0), (a, b)]
  378. >>> p = Polygon(p1, p2, p3, p4)
  379. >>> p.first_moment_of_area()
  380. (625, 3125)
  381. >>> p.first_moment_of_area(point=Point(30, 7))
  382. (525, 3000)
  383. """
  384. if point:
  385. xc, yc = self.centroid
  386. else:
  387. point = self.centroid
  388. xc, yc = point
  389. h_line = Line(point, slope=0)
  390. v_line = Line(point, slope=S.Infinity)
  391. h_poly = self.cut_section(h_line)
  392. v_poly = self.cut_section(v_line)
  393. poly_1 = h_poly[0] if h_poly[0].area <= h_poly[1].area else h_poly[1]
  394. poly_2 = v_poly[0] if v_poly[0].area <= v_poly[1].area else v_poly[1]
  395. Q_x = (poly_1.centroid.y - yc)*poly_1.area
  396. Q_y = (poly_2.centroid.x - xc)*poly_2.area
  397. return Q_x, Q_y
  398. def polar_second_moment_of_area(self):
  399. """Returns the polar modulus of a two-dimensional polygon
  400. It is a constituent of the second moment of area, linked through
  401. the perpendicular axis theorem. While the planar second moment of
  402. area describes an object's resistance to deflection (bending) when
  403. subjected to a force applied to a plane parallel to the central
  404. axis, the polar second moment of area describes an object's
  405. resistance to deflection when subjected to a moment applied in a
  406. plane perpendicular to the object's central axis (i.e. parallel to
  407. the cross-section)
  408. Examples
  409. ========
  410. >>> from sympy import Polygon, symbols
  411. >>> a, b = symbols('a, b')
  412. >>> rectangle = Polygon((0, 0), (a, 0), (a, b), (0, b))
  413. >>> rectangle.polar_second_moment_of_area()
  414. a**3*b/12 + a*b**3/12
  415. References
  416. ==========
  417. .. [1] https://en.wikipedia.org/wiki/Polar_moment_of_inertia
  418. """
  419. second_moment = self.second_moment_of_area()
  420. return second_moment[0] + second_moment[1]
  421. def section_modulus(self, point=None):
  422. """Returns a tuple with the section modulus of a two-dimensional
  423. polygon.
  424. Section modulus is a geometric property of a polygon defined as the
  425. ratio of second moment of area to the distance of the extreme end of
  426. the polygon from the centroidal axis.
  427. Parameters
  428. ==========
  429. point : Point, two-tuple of sympifyable objects, or None(default=None)
  430. point is the point at which section modulus is to be found.
  431. If "point=None" it will be calculated for the point farthest from the
  432. centroidal axis of the polygon.
  433. Returns
  434. =======
  435. S_x, S_y: numbers or SymPy expressions
  436. S_x is the section modulus with respect to the x-axis
  437. S_y is the section modulus with respect to the y-axis
  438. A negative sign indicates that the section modulus is
  439. determined for a point below the centroidal axis
  440. Examples
  441. ========
  442. >>> from sympy import symbols, Polygon, Point
  443. >>> a, b = symbols('a, b', positive=True)
  444. >>> rectangle = Polygon((0, 0), (a, 0), (a, b), (0, b))
  445. >>> rectangle.section_modulus()
  446. (a*b**2/6, a**2*b/6)
  447. >>> rectangle.section_modulus(Point(a/4, b/4))
  448. (-a*b**2/3, -a**2*b/3)
  449. References
  450. ==========
  451. .. [1] https://en.wikipedia.org/wiki/Section_modulus
  452. """
  453. x_c, y_c = self.centroid
  454. if point is None:
  455. # taking x and y as maximum distances from centroid
  456. x_min, y_min, x_max, y_max = self.bounds
  457. y = max(y_c - y_min, y_max - y_c)
  458. x = max(x_c - x_min, x_max - x_c)
  459. else:
  460. # taking x and y as distances of the given point from the centroid
  461. y = point.y - y_c
  462. x = point.x - x_c
  463. second_moment= self.second_moment_of_area()
  464. S_x = second_moment[0]/y
  465. S_y = second_moment[1]/x
  466. return S_x, S_y
  467. @property
  468. def sides(self):
  469. """The directed line segments that form the sides of the polygon.
  470. Returns
  471. =======
  472. sides : list of sides
  473. Each side is a directed Segment.
  474. See Also
  475. ========
  476. sympy.geometry.point.Point, sympy.geometry.line.Segment
  477. Examples
  478. ========
  479. >>> from sympy import Point, Polygon
  480. >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)])
  481. >>> poly = Polygon(p1, p2, p3, p4)
  482. >>> poly.sides
  483. [Segment2D(Point2D(0, 0), Point2D(1, 0)),
  484. Segment2D(Point2D(1, 0), Point2D(5, 1)),
  485. Segment2D(Point2D(5, 1), Point2D(0, 1)), Segment2D(Point2D(0, 1), Point2D(0, 0))]
  486. """
  487. res = []
  488. args = self.vertices
  489. for i in range(-len(args), 0):
  490. res.append(Segment(args[i], args[i + 1]))
  491. return res
  492. @property
  493. def bounds(self):
  494. """Return a tuple (xmin, ymin, xmax, ymax) representing the bounding
  495. rectangle for the geometric figure.
  496. """
  497. verts = self.vertices
  498. xs = [p.x for p in verts]
  499. ys = [p.y for p in verts]
  500. return (min(xs), min(ys), max(xs), max(ys))
  501. def is_convex(self):
  502. """Is the polygon convex?
  503. A polygon is convex if all its interior angles are less than 180
  504. degrees and there are no intersections between sides.
  505. Returns
  506. =======
  507. is_convex : boolean
  508. True if this polygon is convex, False otherwise.
  509. See Also
  510. ========
  511. sympy.geometry.util.convex_hull
  512. Examples
  513. ========
  514. >>> from sympy import Point, Polygon
  515. >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)])
  516. >>> poly = Polygon(p1, p2, p3, p4)
  517. >>> poly.is_convex()
  518. True
  519. """
  520. # Determine orientation of points
  521. args = self.vertices
  522. cw = self._isright(args[-2], args[-1], args[0])
  523. for i in range(1, len(args)):
  524. if cw ^ self._isright(args[i - 2], args[i - 1], args[i]):
  525. return False
  526. # check for intersecting sides
  527. sides = self.sides
  528. for i, si in enumerate(sides):
  529. pts = si.args
  530. # exclude the sides connected to si
  531. for j in range(1 if i == len(sides) - 1 else 0, i - 1):
  532. sj = sides[j]
  533. if sj.p1 not in pts and sj.p2 not in pts:
  534. hit = si.intersection(sj)
  535. if hit:
  536. return False
  537. return True
  538. def encloses_point(self, p):
  539. """
  540. Return True if p is enclosed by (is inside of) self.
  541. Notes
  542. =====
  543. Being on the border of self is considered False.
  544. Parameters
  545. ==========
  546. p : Point
  547. Returns
  548. =======
  549. encloses_point : True, False or None
  550. See Also
  551. ========
  552. sympy.geometry.point.Point, sympy.geometry.ellipse.Ellipse.encloses_point
  553. Examples
  554. ========
  555. >>> from sympy import Polygon, Point
  556. >>> p = Polygon((0, 0), (4, 0), (4, 4))
  557. >>> p.encloses_point(Point(2, 1))
  558. True
  559. >>> p.encloses_point(Point(2, 2))
  560. False
  561. >>> p.encloses_point(Point(5, 5))
  562. False
  563. References
  564. ==========
  565. .. [1] http://paulbourke.net/geometry/polygonmesh/#insidepoly
  566. """
  567. p = Point(p, dim=2)
  568. if p in self.vertices or any(p in s for s in self.sides):
  569. return False
  570. # move to p, checking that the result is numeric
  571. lit = []
  572. for v in self.vertices:
  573. lit.append(v - p) # the difference is simplified
  574. if lit[-1].free_symbols:
  575. return None
  576. poly = Polygon(*lit)
  577. # polygon closure is assumed in the following test but Polygon removes duplicate pts so
  578. # the last point has to be added so all sides are computed. Using Polygon.sides is
  579. # not good since Segments are unordered.
  580. args = poly.args
  581. indices = list(range(-len(args), 1))
  582. if poly.is_convex():
  583. orientation = None
  584. for i in indices:
  585. a = args[i]
  586. b = args[i + 1]
  587. test = ((-a.y)*(b.x - a.x) - (-a.x)*(b.y - a.y)).is_negative
  588. if orientation is None:
  589. orientation = test
  590. elif test is not orientation:
  591. return False
  592. return True
  593. hit_odd = False
  594. p1x, p1y = args[0].args
  595. for i in indices[1:]:
  596. p2x, p2y = args[i].args
  597. if 0 > min(p1y, p2y):
  598. if 0 <= max(p1y, p2y):
  599. if 0 <= max(p1x, p2x):
  600. if p1y != p2y:
  601. xinters = (-p1y)*(p2x - p1x)/(p2y - p1y) + p1x
  602. if p1x == p2x or 0 <= xinters:
  603. hit_odd = not hit_odd
  604. p1x, p1y = p2x, p2y
  605. return hit_odd
  606. def arbitrary_point(self, parameter='t'):
  607. """A parameterized point on the polygon.
  608. The parameter, varying from 0 to 1, assigns points to the position on
  609. the perimeter that is that fraction of the total perimeter. So the
  610. point evaluated at t=1/2 would return the point from the first vertex
  611. that is 1/2 way around the polygon.
  612. Parameters
  613. ==========
  614. parameter : str, optional
  615. Default value is 't'.
  616. Returns
  617. =======
  618. arbitrary_point : Point
  619. Raises
  620. ======
  621. ValueError
  622. When `parameter` already appears in the Polygon's definition.
  623. See Also
  624. ========
  625. sympy.geometry.point.Point
  626. Examples
  627. ========
  628. >>> from sympy import Polygon, Symbol
  629. >>> t = Symbol('t', real=True)
  630. >>> tri = Polygon((0, 0), (1, 0), (1, 1))
  631. >>> p = tri.arbitrary_point('t')
  632. >>> perimeter = tri.perimeter
  633. >>> s1, s2 = [s.length for s in tri.sides[:2]]
  634. >>> p.subs(t, (s1 + s2/2)/perimeter)
  635. Point2D(1, 1/2)
  636. """
  637. t = _symbol(parameter, real=True)
  638. if t.name in (f.name for f in self.free_symbols):
  639. raise ValueError('Symbol %s already appears in object and cannot be used as a parameter.' % t.name)
  640. sides = []
  641. perimeter = self.perimeter
  642. perim_fraction_start = 0
  643. for s in self.sides:
  644. side_perim_fraction = s.length/perimeter
  645. perim_fraction_end = perim_fraction_start + side_perim_fraction
  646. pt = s.arbitrary_point(parameter).subs(
  647. t, (t - perim_fraction_start)/side_perim_fraction)
  648. sides.append(
  649. (pt, (And(perim_fraction_start <= t, t < perim_fraction_end))))
  650. perim_fraction_start = perim_fraction_end
  651. return Piecewise(*sides)
  652. def parameter_value(self, other, t):
  653. if not isinstance(other,GeometryEntity):
  654. other = Point(other, dim=self.ambient_dimension)
  655. if not isinstance(other,Point):
  656. raise ValueError("other must be a point")
  657. if other.free_symbols:
  658. raise NotImplementedError('non-numeric coordinates')
  659. unknown = False
  660. T = Dummy('t', real=True)
  661. p = self.arbitrary_point(T)
  662. for pt, cond in p.args:
  663. sol = solve(pt - other, T, dict=True)
  664. if not sol:
  665. continue
  666. value = sol[0][T]
  667. if simplify(cond.subs(T, value)) == True:
  668. return {t: value}
  669. unknown = True
  670. if unknown:
  671. raise ValueError("Given point may not be on %s" % func_name(self))
  672. raise ValueError("Given point is not on %s" % func_name(self))
  673. def plot_interval(self, parameter='t'):
  674. """The plot interval for the default geometric plot of the polygon.
  675. Parameters
  676. ==========
  677. parameter : str, optional
  678. Default value is 't'.
  679. Returns
  680. =======
  681. plot_interval : list (plot interval)
  682. [parameter, lower_bound, upper_bound]
  683. Examples
  684. ========
  685. >>> from sympy import Polygon
  686. >>> p = Polygon((0, 0), (1, 0), (1, 1))
  687. >>> p.plot_interval()
  688. [t, 0, 1]
  689. """
  690. t = Symbol(parameter, real=True)
  691. return [t, 0, 1]
  692. def intersection(self, o):
  693. """The intersection of polygon and geometry entity.
  694. The intersection may be empty and can contain individual Points and
  695. complete Line Segments.
  696. Parameters
  697. ==========
  698. other: GeometryEntity
  699. Returns
  700. =======
  701. intersection : list
  702. The list of Segments and Points
  703. See Also
  704. ========
  705. sympy.geometry.point.Point, sympy.geometry.line.Segment
  706. Examples
  707. ========
  708. >>> from sympy import Point, Polygon, Line
  709. >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)])
  710. >>> poly1 = Polygon(p1, p2, p3, p4)
  711. >>> p5, p6, p7 = map(Point, [(3, 2), (1, -1), (0, 2)])
  712. >>> poly2 = Polygon(p5, p6, p7)
  713. >>> poly1.intersection(poly2)
  714. [Point2D(1/3, 1), Point2D(2/3, 0), Point2D(9/5, 1/5), Point2D(7/3, 1)]
  715. >>> poly1.intersection(Line(p1, p2))
  716. [Segment2D(Point2D(0, 0), Point2D(1, 0))]
  717. >>> poly1.intersection(p1)
  718. [Point2D(0, 0)]
  719. """
  720. intersection_result = []
  721. k = o.sides if isinstance(o, Polygon) else [o]
  722. for side in self.sides:
  723. for side1 in k:
  724. intersection_result.extend(side.intersection(side1))
  725. intersection_result = list(uniq(intersection_result))
  726. points = [entity for entity in intersection_result if isinstance(entity, Point)]
  727. segments = [entity for entity in intersection_result if isinstance(entity, Segment)]
  728. if points and segments:
  729. points_in_segments = list(uniq([point for point in points for segment in segments if point in segment]))
  730. if points_in_segments:
  731. for i in points_in_segments:
  732. points.remove(i)
  733. return list(ordered(segments + points))
  734. else:
  735. return list(ordered(intersection_result))
  736. def cut_section(self, line):
  737. """
  738. Returns a tuple of two polygon segments that lie above and below
  739. the intersecting line respectively.
  740. Parameters
  741. ==========
  742. line: Line object of geometry module
  743. line which cuts the Polygon. The part of the Polygon that lies
  744. above and below this line is returned.
  745. Returns
  746. =======
  747. upper_polygon, lower_polygon: Polygon objects or None
  748. upper_polygon is the polygon that lies above the given line.
  749. lower_polygon is the polygon that lies below the given line.
  750. upper_polygon and lower polygon are ``None`` when no polygon
  751. exists above the line or below the line.
  752. Raises
  753. ======
  754. ValueError: When the line does not intersect the polygon
  755. Examples
  756. ========
  757. >>> from sympy import Polygon, Line
  758. >>> a, b = 20, 10
  759. >>> p1, p2, p3, p4 = [(0, b), (0, 0), (a, 0), (a, b)]
  760. >>> rectangle = Polygon(p1, p2, p3, p4)
  761. >>> t = rectangle.cut_section(Line((0, 5), slope=0))
  762. >>> t
  763. (Polygon(Point2D(0, 10), Point2D(0, 5), Point2D(20, 5), Point2D(20, 10)),
  764. Polygon(Point2D(0, 5), Point2D(0, 0), Point2D(20, 0), Point2D(20, 5)))
  765. >>> upper_segment, lower_segment = t
  766. >>> upper_segment.area
  767. 100
  768. >>> upper_segment.centroid
  769. Point2D(10, 15/2)
  770. >>> lower_segment.centroid
  771. Point2D(10, 5/2)
  772. References
  773. ==========
  774. .. [1] https://github.com/sympy/sympy/wiki/A-method-to-return-a-cut-section-of-any-polygon-geometry
  775. """
  776. intersection_points = self.intersection(line)
  777. if not intersection_points:
  778. raise ValueError("This line does not intersect the polygon")
  779. points = list(self.vertices)
  780. points.append(points[0])
  781. x, y = symbols('x, y', real=True, cls=Dummy)
  782. eq = line.equation(x, y)
  783. # considering equation of line to be `ax +by + c`
  784. a = eq.coeff(x)
  785. b = eq.coeff(y)
  786. upper_vertices = []
  787. lower_vertices = []
  788. # prev is true when previous point is above the line
  789. prev = True
  790. prev_point = None
  791. for point in points:
  792. # when coefficient of y is 0, right side of the line is
  793. # considered
  794. compare = eq.subs({x: point.x, y: point.y})/b if b \
  795. else eq.subs(x, point.x)/a
  796. # if point lies above line
  797. if compare > 0:
  798. if not prev:
  799. # if previous point lies below the line, the intersection
  800. # point of the polygon egde and the line has to be included
  801. edge = Line(point, prev_point)
  802. new_point = edge.intersection(line)
  803. upper_vertices.append(new_point[0])
  804. lower_vertices.append(new_point[0])
  805. upper_vertices.append(point)
  806. prev = True
  807. else:
  808. if prev and prev_point:
  809. edge = Line(point, prev_point)
  810. new_point = edge.intersection(line)
  811. upper_vertices.append(new_point[0])
  812. lower_vertices.append(new_point[0])
  813. lower_vertices.append(point)
  814. prev = False
  815. prev_point = point
  816. upper_polygon, lower_polygon = None, None
  817. if upper_vertices and isinstance(Polygon(*upper_vertices), Polygon):
  818. upper_polygon = Polygon(*upper_vertices)
  819. if lower_vertices and isinstance(Polygon(*lower_vertices), Polygon):
  820. lower_polygon = Polygon(*lower_vertices)
  821. return upper_polygon, lower_polygon
  822. def distance(self, o):
  823. """
  824. Returns the shortest distance between self and o.
  825. If o is a point, then self does not need to be convex.
  826. If o is another polygon self and o must be convex.
  827. Examples
  828. ========
  829. >>> from sympy import Point, Polygon, RegularPolygon
  830. >>> p1, p2 = map(Point, [(0, 0), (7, 5)])
  831. >>> poly = Polygon(*RegularPolygon(p1, 1, 3).vertices)
  832. >>> poly.distance(p2)
  833. sqrt(61)
  834. """
  835. if isinstance(o, Point):
  836. dist = oo
  837. for side in self.sides:
  838. current = side.distance(o)
  839. if current == 0:
  840. return S.Zero
  841. elif current < dist:
  842. dist = current
  843. return dist
  844. elif isinstance(o, Polygon) and self.is_convex() and o.is_convex():
  845. return self._do_poly_distance(o)
  846. raise NotImplementedError()
  847. def _do_poly_distance(self, e2):
  848. """
  849. Calculates the least distance between the exteriors of two
  850. convex polygons e1 and e2. Does not check for the convexity
  851. of the polygons as this is checked by Polygon.distance.
  852. Notes
  853. =====
  854. - Prints a warning if the two polygons possibly intersect as the return
  855. value will not be valid in such a case. For a more through test of
  856. intersection use intersection().
  857. See Also
  858. ========
  859. sympy.geometry.point.Point.distance
  860. Examples
  861. ========
  862. >>> from sympy import Point, Polygon
  863. >>> square = Polygon(Point(0, 0), Point(0, 1), Point(1, 1), Point(1, 0))
  864. >>> triangle = Polygon(Point(1, 2), Point(2, 2), Point(2, 1))
  865. >>> square._do_poly_distance(triangle)
  866. sqrt(2)/2
  867. Description of method used
  868. ==========================
  869. Method:
  870. [1] http://cgm.cs.mcgill.ca/~orm/mind2p.html
  871. Uses rotating calipers:
  872. [2] https://en.wikipedia.org/wiki/Rotating_calipers
  873. and antipodal points:
  874. [3] https://en.wikipedia.org/wiki/Antipodal_point
  875. """
  876. e1 = self
  877. '''Tests for a possible intersection between the polygons and outputs a warning'''
  878. e1_center = e1.centroid
  879. e2_center = e2.centroid
  880. e1_max_radius = S.Zero
  881. e2_max_radius = S.Zero
  882. for vertex in e1.vertices:
  883. r = Point.distance(e1_center, vertex)
  884. if e1_max_radius < r:
  885. e1_max_radius = r
  886. for vertex in e2.vertices:
  887. r = Point.distance(e2_center, vertex)
  888. if e2_max_radius < r:
  889. e2_max_radius = r
  890. center_dist = Point.distance(e1_center, e2_center)
  891. if center_dist <= e1_max_radius + e2_max_radius:
  892. warnings.warn("Polygons may intersect producing erroneous output",
  893. stacklevel=3)
  894. '''
  895. Find the upper rightmost vertex of e1 and the lowest leftmost vertex of e2
  896. '''
  897. e1_ymax = Point(0, -oo)
  898. e2_ymin = Point(0, oo)
  899. for vertex in e1.vertices:
  900. if vertex.y > e1_ymax.y or (vertex.y == e1_ymax.y and vertex.x > e1_ymax.x):
  901. e1_ymax = vertex
  902. for vertex in e2.vertices:
  903. if vertex.y < e2_ymin.y or (vertex.y == e2_ymin.y and vertex.x < e2_ymin.x):
  904. e2_ymin = vertex
  905. min_dist = Point.distance(e1_ymax, e2_ymin)
  906. '''
  907. Produce a dictionary with vertices of e1 as the keys and, for each vertex, the points
  908. to which the vertex is connected as its value. The same is then done for e2.
  909. '''
  910. e1_connections = {}
  911. e2_connections = {}
  912. for side in e1.sides:
  913. if side.p1 in e1_connections:
  914. e1_connections[side.p1].append(side.p2)
  915. else:
  916. e1_connections[side.p1] = [side.p2]
  917. if side.p2 in e1_connections:
  918. e1_connections[side.p2].append(side.p1)
  919. else:
  920. e1_connections[side.p2] = [side.p1]
  921. for side in e2.sides:
  922. if side.p1 in e2_connections:
  923. e2_connections[side.p1].append(side.p2)
  924. else:
  925. e2_connections[side.p1] = [side.p2]
  926. if side.p2 in e2_connections:
  927. e2_connections[side.p2].append(side.p1)
  928. else:
  929. e2_connections[side.p2] = [side.p1]
  930. e1_current = e1_ymax
  931. e2_current = e2_ymin
  932. support_line = Line(Point(S.Zero, S.Zero), Point(S.One, S.Zero))
  933. '''
  934. Determine which point in e1 and e2 will be selected after e2_ymin and e1_ymax,
  935. this information combined with the above produced dictionaries determines the
  936. path that will be taken around the polygons
  937. '''
  938. point1 = e1_connections[e1_ymax][0]
  939. point2 = e1_connections[e1_ymax][1]
  940. angle1 = support_line.angle_between(Line(e1_ymax, point1))
  941. angle2 = support_line.angle_between(Line(e1_ymax, point2))
  942. if angle1 < angle2:
  943. e1_next = point1
  944. elif angle2 < angle1:
  945. e1_next = point2
  946. elif Point.distance(e1_ymax, point1) > Point.distance(e1_ymax, point2):
  947. e1_next = point2
  948. else:
  949. e1_next = point1
  950. point1 = e2_connections[e2_ymin][0]
  951. point2 = e2_connections[e2_ymin][1]
  952. angle1 = support_line.angle_between(Line(e2_ymin, point1))
  953. angle2 = support_line.angle_between(Line(e2_ymin, point2))
  954. if angle1 > angle2:
  955. e2_next = point1
  956. elif angle2 > angle1:
  957. e2_next = point2
  958. elif Point.distance(e2_ymin, point1) > Point.distance(e2_ymin, point2):
  959. e2_next = point2
  960. else:
  961. e2_next = point1
  962. '''
  963. Loop which determines the distance between anti-podal pairs and updates the
  964. minimum distance accordingly. It repeats until it reaches the starting position.
  965. '''
  966. while True:
  967. e1_angle = support_line.angle_between(Line(e1_current, e1_next))
  968. e2_angle = pi - support_line.angle_between(Line(
  969. e2_current, e2_next))
  970. if (e1_angle < e2_angle) is True:
  971. support_line = Line(e1_current, e1_next)
  972. e1_segment = Segment(e1_current, e1_next)
  973. min_dist_current = e1_segment.distance(e2_current)
  974. if min_dist_current.evalf() < min_dist.evalf():
  975. min_dist = min_dist_current
  976. if e1_connections[e1_next][0] != e1_current:
  977. e1_current = e1_next
  978. e1_next = e1_connections[e1_next][0]
  979. else:
  980. e1_current = e1_next
  981. e1_next = e1_connections[e1_next][1]
  982. elif (e1_angle > e2_angle) is True:
  983. support_line = Line(e2_next, e2_current)
  984. e2_segment = Segment(e2_current, e2_next)
  985. min_dist_current = e2_segment.distance(e1_current)
  986. if min_dist_current.evalf() < min_dist.evalf():
  987. min_dist = min_dist_current
  988. if e2_connections[e2_next][0] != e2_current:
  989. e2_current = e2_next
  990. e2_next = e2_connections[e2_next][0]
  991. else:
  992. e2_current = e2_next
  993. e2_next = e2_connections[e2_next][1]
  994. else:
  995. support_line = Line(e1_current, e1_next)
  996. e1_segment = Segment(e1_current, e1_next)
  997. e2_segment = Segment(e2_current, e2_next)
  998. min1 = e1_segment.distance(e2_next)
  999. min2 = e2_segment.distance(e1_next)
  1000. min_dist_current = min(min1, min2)
  1001. if min_dist_current.evalf() < min_dist.evalf():
  1002. min_dist = min_dist_current
  1003. if e1_connections[e1_next][0] != e1_current:
  1004. e1_current = e1_next
  1005. e1_next = e1_connections[e1_next][0]
  1006. else:
  1007. e1_current = e1_next
  1008. e1_next = e1_connections[e1_next][1]
  1009. if e2_connections[e2_next][0] != e2_current:
  1010. e2_current = e2_next
  1011. e2_next = e2_connections[e2_next][0]
  1012. else:
  1013. e2_current = e2_next
  1014. e2_next = e2_connections[e2_next][1]
  1015. if e1_current == e1_ymax and e2_current == e2_ymin:
  1016. break
  1017. return min_dist
  1018. def _svg(self, scale_factor=1., fill_color="#66cc99"):
  1019. """Returns SVG path element for the Polygon.
  1020. Parameters
  1021. ==========
  1022. scale_factor : float
  1023. Multiplication factor for the SVG stroke-width. Default is 1.
  1024. fill_color : str, optional
  1025. Hex string for fill color. Default is "#66cc99".
  1026. """
  1027. verts = map(N, self.vertices)
  1028. coords = ["{},{}".format(p.x, p.y) for p in verts]
  1029. path = "M {} L {} z".format(coords[0], " L ".join(coords[1:]))
  1030. return (
  1031. '<path fill-rule="evenodd" fill="{2}" stroke="#555555" '
  1032. 'stroke-width="{0}" opacity="0.6" d="{1}" />'
  1033. ).format(2. * scale_factor, path, fill_color)
  1034. def _hashable_content(self):
  1035. D = {}
  1036. def ref_list(point_list):
  1037. kee = {}
  1038. for i, p in enumerate(ordered(set(point_list))):
  1039. kee[p] = i
  1040. D[i] = p
  1041. return [kee[p] for p in point_list]
  1042. S1 = ref_list(self.args)
  1043. r_nor = rotate_left(S1, least_rotation(S1))
  1044. S2 = ref_list(list(reversed(self.args)))
  1045. r_rev = rotate_left(S2, least_rotation(S2))
  1046. if r_nor < r_rev:
  1047. r = r_nor
  1048. else:
  1049. r = r_rev
  1050. canonical_args = [ D[order] for order in r ]
  1051. return tuple(canonical_args)
  1052. def __contains__(self, o):
  1053. """
  1054. Return True if o is contained within the boundary lines of self.altitudes
  1055. Parameters
  1056. ==========
  1057. other : GeometryEntity
  1058. Returns
  1059. =======
  1060. contained in : bool
  1061. The points (and sides, if applicable) are contained in self.
  1062. See Also
  1063. ========
  1064. sympy.geometry.entity.GeometryEntity.encloses
  1065. Examples
  1066. ========
  1067. >>> from sympy import Line, Segment, Point
  1068. >>> p = Point(0, 0)
  1069. >>> q = Point(1, 1)
  1070. >>> s = Segment(p, q*2)
  1071. >>> l = Line(p, q)
  1072. >>> p in q
  1073. False
  1074. >>> p in s
  1075. True
  1076. >>> q*3 in s
  1077. False
  1078. >>> s in l
  1079. True
  1080. """
  1081. if isinstance(o, Polygon):
  1082. return self == o
  1083. elif isinstance(o, Segment):
  1084. return any(o in s for s in self.sides)
  1085. elif isinstance(o, Point):
  1086. if o in self.vertices:
  1087. return True
  1088. for side in self.sides:
  1089. if o in side:
  1090. return True
  1091. return False
  1092. def bisectors(p, prec=None):
  1093. """Returns angle bisectors of a polygon. If prec is given
  1094. then approximate the point defining the ray to that precision.
  1095. The distance between the points defining the bisector ray is 1.
  1096. Examples
  1097. ========
  1098. >>> from sympy import Polygon, Point
  1099. >>> p = Polygon(Point(0, 0), Point(2, 0), Point(1, 1), Point(0, 3))
  1100. >>> p.bisectors(2)
  1101. {Point2D(0, 0): Ray2D(Point2D(0, 0), Point2D(0.71, 0.71)),
  1102. Point2D(0, 3): Ray2D(Point2D(0, 3), Point2D(0.23, 2.0)),
  1103. Point2D(1, 1): Ray2D(Point2D(1, 1), Point2D(0.19, 0.42)),
  1104. Point2D(2, 0): Ray2D(Point2D(2, 0), Point2D(1.1, 0.38))}
  1105. """
  1106. b = {}
  1107. pts = list(p.args)
  1108. pts.append(pts[0]) # close it
  1109. cw = Polygon._isright(*pts[:3])
  1110. if cw:
  1111. pts = list(reversed(pts))
  1112. for v, a in p.angles.items():
  1113. i = pts.index(v)
  1114. p1, p2 = Point._normalize_dimension(pts[i], pts[i + 1])
  1115. ray = Ray(p1, p2).rotate(a/2, v)
  1116. dir = ray.direction
  1117. ray = Ray(ray.p1, ray.p1 + dir/dir.distance((0, 0)))
  1118. if prec is not None:
  1119. ray = Ray(ray.p1, ray.p2.n(prec))
  1120. b[v] = ray
  1121. return b
  1122. class RegularPolygon(Polygon):
  1123. """
  1124. A regular polygon.
  1125. Such a polygon has all internal angles equal and all sides the same length.
  1126. Parameters
  1127. ==========
  1128. center : Point
  1129. radius : number or Basic instance
  1130. The distance from the center to a vertex
  1131. n : int
  1132. The number of sides
  1133. Attributes
  1134. ==========
  1135. vertices
  1136. center
  1137. radius
  1138. rotation
  1139. apothem
  1140. interior_angle
  1141. exterior_angle
  1142. circumcircle
  1143. incircle
  1144. angles
  1145. Raises
  1146. ======
  1147. GeometryError
  1148. If the `center` is not a Point, or the `radius` is not a number or Basic
  1149. instance, or the number of sides, `n`, is less than three.
  1150. Notes
  1151. =====
  1152. A RegularPolygon can be instantiated with Polygon with the kwarg n.
  1153. Regular polygons are instantiated with a center, radius, number of sides
  1154. and a rotation angle. Whereas the arguments of a Polygon are vertices, the
  1155. vertices of the RegularPolygon must be obtained with the vertices method.
  1156. See Also
  1157. ========
  1158. sympy.geometry.point.Point, Polygon
  1159. Examples
  1160. ========
  1161. >>> from sympy import RegularPolygon, Point
  1162. >>> r = RegularPolygon(Point(0, 0), 5, 3)
  1163. >>> r
  1164. RegularPolygon(Point2D(0, 0), 5, 3, 0)
  1165. >>> r.vertices[0]
  1166. Point2D(5, 0)
  1167. """
  1168. __slots__ = ('_n', '_center', '_radius', '_rot')
  1169. def __new__(self, c, r, n, rot=0, **kwargs):
  1170. r, n, rot = map(sympify, (r, n, rot))
  1171. c = Point(c, dim=2, **kwargs)
  1172. if not isinstance(r, Expr):
  1173. raise GeometryError("r must be an Expr object, not %s" % r)
  1174. if n.is_Number:
  1175. as_int(n) # let an error raise if necessary
  1176. if n < 3:
  1177. raise GeometryError("n must be a >= 3, not %s" % n)
  1178. obj = GeometryEntity.__new__(self, c, r, n, **kwargs)
  1179. obj._n = n
  1180. obj._center = c
  1181. obj._radius = r
  1182. obj._rot = rot % (2*S.Pi/n) if rot.is_number else rot
  1183. return obj
  1184. def _eval_evalf(self, prec=15, **options):
  1185. c, r, n, a = self.args
  1186. dps = prec_to_dps(prec)
  1187. c, r, a = [i.evalf(n=dps, **options) for i in (c, r, a)]
  1188. return self.func(c, r, n, a)
  1189. @property
  1190. def args(self):
  1191. """
  1192. Returns the center point, the radius,
  1193. the number of sides, and the orientation angle.
  1194. Examples
  1195. ========
  1196. >>> from sympy import RegularPolygon, Point
  1197. >>> r = RegularPolygon(Point(0, 0), 5, 3)
  1198. >>> r.args
  1199. (Point2D(0, 0), 5, 3, 0)
  1200. """
  1201. return self._center, self._radius, self._n, self._rot
  1202. def __str__(self):
  1203. return 'RegularPolygon(%s, %s, %s, %s)' % tuple(self.args)
  1204. def __repr__(self):
  1205. return 'RegularPolygon(%s, %s, %s, %s)' % tuple(self.args)
  1206. @property
  1207. def area(self):
  1208. """Returns the area.
  1209. Examples
  1210. ========
  1211. >>> from sympy import RegularPolygon
  1212. >>> square = RegularPolygon((0, 0), 1, 4)
  1213. >>> square.area
  1214. 2
  1215. >>> _ == square.length**2
  1216. True
  1217. """
  1218. c, r, n, rot = self.args
  1219. return sign(r)*n*self.length**2/(4*tan(pi/n))
  1220. @property
  1221. def length(self):
  1222. """Returns the length of the sides.
  1223. The half-length of the side and the apothem form two legs
  1224. of a right triangle whose hypotenuse is the radius of the
  1225. regular polygon.
  1226. Examples
  1227. ========
  1228. >>> from sympy import RegularPolygon
  1229. >>> from sympy import sqrt
  1230. >>> s = square_in_unit_circle = RegularPolygon((0, 0), 1, 4)
  1231. >>> s.length
  1232. sqrt(2)
  1233. >>> sqrt((_/2)**2 + s.apothem**2) == s.radius
  1234. True
  1235. """
  1236. return self.radius*2*sin(pi/self._n)
  1237. @property
  1238. def center(self):
  1239. """The center of the RegularPolygon
  1240. This is also the center of the circumscribing circle.
  1241. Returns
  1242. =======
  1243. center : Point
  1244. See Also
  1245. ========
  1246. sympy.geometry.point.Point, sympy.geometry.ellipse.Ellipse.center
  1247. Examples
  1248. ========
  1249. >>> from sympy import RegularPolygon, Point
  1250. >>> rp = RegularPolygon(Point(0, 0), 5, 4)
  1251. >>> rp.center
  1252. Point2D(0, 0)
  1253. """
  1254. return self._center
  1255. centroid = center
  1256. @property
  1257. def circumcenter(self):
  1258. """
  1259. Alias for center.
  1260. Examples
  1261. ========
  1262. >>> from sympy import RegularPolygon, Point
  1263. >>> rp = RegularPolygon(Point(0, 0), 5, 4)
  1264. >>> rp.circumcenter
  1265. Point2D(0, 0)
  1266. """
  1267. return self.center
  1268. @property
  1269. def radius(self):
  1270. """Radius of the RegularPolygon
  1271. This is also the radius of the circumscribing circle.
  1272. Returns
  1273. =======
  1274. radius : number or instance of Basic
  1275. See Also
  1276. ========
  1277. sympy.geometry.line.Segment.length, sympy.geometry.ellipse.Circle.radius
  1278. Examples
  1279. ========
  1280. >>> from sympy import Symbol
  1281. >>> from sympy import RegularPolygon, Point
  1282. >>> radius = Symbol('r')
  1283. >>> rp = RegularPolygon(Point(0, 0), radius, 4)
  1284. >>> rp.radius
  1285. r
  1286. """
  1287. return self._radius
  1288. @property
  1289. def circumradius(self):
  1290. """
  1291. Alias for radius.
  1292. Examples
  1293. ========
  1294. >>> from sympy import Symbol
  1295. >>> from sympy import RegularPolygon, Point
  1296. >>> radius = Symbol('r')
  1297. >>> rp = RegularPolygon(Point(0, 0), radius, 4)
  1298. >>> rp.circumradius
  1299. r
  1300. """
  1301. return self.radius
  1302. @property
  1303. def rotation(self):
  1304. """CCW angle by which the RegularPolygon is rotated
  1305. Returns
  1306. =======
  1307. rotation : number or instance of Basic
  1308. Examples
  1309. ========
  1310. >>> from sympy import pi
  1311. >>> from sympy.abc import a
  1312. >>> from sympy import RegularPolygon, Point
  1313. >>> RegularPolygon(Point(0, 0), 3, 4, pi/4).rotation
  1314. pi/4
  1315. Numerical rotation angles are made canonical:
  1316. >>> RegularPolygon(Point(0, 0), 3, 4, a).rotation
  1317. a
  1318. >>> RegularPolygon(Point(0, 0), 3, 4, pi).rotation
  1319. 0
  1320. """
  1321. return self._rot
  1322. @property
  1323. def apothem(self):
  1324. """The inradius of the RegularPolygon.
  1325. The apothem/inradius is the radius of the inscribed circle.
  1326. Returns
  1327. =======
  1328. apothem : number or instance of Basic
  1329. See Also
  1330. ========
  1331. sympy.geometry.line.Segment.length, sympy.geometry.ellipse.Circle.radius
  1332. Examples
  1333. ========
  1334. >>> from sympy import Symbol
  1335. >>> from sympy import RegularPolygon, Point
  1336. >>> radius = Symbol('r')
  1337. >>> rp = RegularPolygon(Point(0, 0), radius, 4)
  1338. >>> rp.apothem
  1339. sqrt(2)*r/2
  1340. """
  1341. return self.radius * cos(S.Pi/self._n)
  1342. @property
  1343. def inradius(self):
  1344. """
  1345. Alias for apothem.
  1346. Examples
  1347. ========
  1348. >>> from sympy import Symbol
  1349. >>> from sympy import RegularPolygon, Point
  1350. >>> radius = Symbol('r')
  1351. >>> rp = RegularPolygon(Point(0, 0), radius, 4)
  1352. >>> rp.inradius
  1353. sqrt(2)*r/2
  1354. """
  1355. return self.apothem
  1356. @property
  1357. def interior_angle(self):
  1358. """Measure of the interior angles.
  1359. Returns
  1360. =======
  1361. interior_angle : number
  1362. See Also
  1363. ========
  1364. sympy.geometry.line.LinearEntity.angle_between
  1365. Examples
  1366. ========
  1367. >>> from sympy import RegularPolygon, Point
  1368. >>> rp = RegularPolygon(Point(0, 0), 4, 8)
  1369. >>> rp.interior_angle
  1370. 3*pi/4
  1371. """
  1372. return (self._n - 2)*S.Pi/self._n
  1373. @property
  1374. def exterior_angle(self):
  1375. """Measure of the exterior angles.
  1376. Returns
  1377. =======
  1378. exterior_angle : number
  1379. See Also
  1380. ========
  1381. sympy.geometry.line.LinearEntity.angle_between
  1382. Examples
  1383. ========
  1384. >>> from sympy import RegularPolygon, Point
  1385. >>> rp = RegularPolygon(Point(0, 0), 4, 8)
  1386. >>> rp.exterior_angle
  1387. pi/4
  1388. """
  1389. return 2*S.Pi/self._n
  1390. @property
  1391. def circumcircle(self):
  1392. """The circumcircle of the RegularPolygon.
  1393. Returns
  1394. =======
  1395. circumcircle : Circle
  1396. See Also
  1397. ========
  1398. circumcenter, sympy.geometry.ellipse.Circle
  1399. Examples
  1400. ========
  1401. >>> from sympy import RegularPolygon, Point
  1402. >>> rp = RegularPolygon(Point(0, 0), 4, 8)
  1403. >>> rp.circumcircle
  1404. Circle(Point2D(0, 0), 4)
  1405. """
  1406. return Circle(self.center, self.radius)
  1407. @property
  1408. def incircle(self):
  1409. """The incircle of the RegularPolygon.
  1410. Returns
  1411. =======
  1412. incircle : Circle
  1413. See Also
  1414. ========
  1415. inradius, sympy.geometry.ellipse.Circle
  1416. Examples
  1417. ========
  1418. >>> from sympy import RegularPolygon, Point
  1419. >>> rp = RegularPolygon(Point(0, 0), 4, 7)
  1420. >>> rp.incircle
  1421. Circle(Point2D(0, 0), 4*cos(pi/7))
  1422. """
  1423. return Circle(self.center, self.apothem)
  1424. @property
  1425. def angles(self):
  1426. """
  1427. Returns a dictionary with keys, the vertices of the Polygon,
  1428. and values, the interior angle at each vertex.
  1429. Examples
  1430. ========
  1431. >>> from sympy import RegularPolygon, Point
  1432. >>> r = RegularPolygon(Point(0, 0), 5, 3)
  1433. >>> r.angles
  1434. {Point2D(-5/2, -5*sqrt(3)/2): pi/3,
  1435. Point2D(-5/2, 5*sqrt(3)/2): pi/3,
  1436. Point2D(5, 0): pi/3}
  1437. """
  1438. ret = {}
  1439. ang = self.interior_angle
  1440. for v in self.vertices:
  1441. ret[v] = ang
  1442. return ret
  1443. def encloses_point(self, p):
  1444. """
  1445. Return True if p is enclosed by (is inside of) self.
  1446. Notes
  1447. =====
  1448. Being on the border of self is considered False.
  1449. The general Polygon.encloses_point method is called only if
  1450. a point is not within or beyond the incircle or circumcircle,
  1451. respectively.
  1452. Parameters
  1453. ==========
  1454. p : Point
  1455. Returns
  1456. =======
  1457. encloses_point : True, False or None
  1458. See Also
  1459. ========
  1460. sympy.geometry.ellipse.Ellipse.encloses_point
  1461. Examples
  1462. ========
  1463. >>> from sympy import RegularPolygon, S, Point, Symbol
  1464. >>> p = RegularPolygon((0, 0), 3, 4)
  1465. >>> p.encloses_point(Point(0, 0))
  1466. True
  1467. >>> r, R = p.inradius, p.circumradius
  1468. >>> p.encloses_point(Point((r + R)/2, 0))
  1469. True
  1470. >>> p.encloses_point(Point(R/2, R/2 + (R - r)/10))
  1471. False
  1472. >>> t = Symbol('t', real=True)
  1473. >>> p.encloses_point(p.arbitrary_point().subs(t, S.Half))
  1474. False
  1475. >>> p.encloses_point(Point(5, 5))
  1476. False
  1477. """
  1478. c = self.center
  1479. d = Segment(c, p).length
  1480. if d >= self.radius:
  1481. return False
  1482. elif d < self.inradius:
  1483. return True
  1484. else:
  1485. # now enumerate the RegularPolygon like a general polygon.
  1486. return Polygon.encloses_point(self, p)
  1487. def spin(self, angle):
  1488. """Increment *in place* the virtual Polygon's rotation by ccw angle.
  1489. See also: rotate method which moves the center.
  1490. >>> from sympy import Polygon, Point, pi
  1491. >>> r = Polygon(Point(0,0), 1, n=3)
  1492. >>> r.vertices[0]
  1493. Point2D(1, 0)
  1494. >>> r.spin(pi/6)
  1495. >>> r.vertices[0]
  1496. Point2D(sqrt(3)/2, 1/2)
  1497. See Also
  1498. ========
  1499. rotation
  1500. rotate : Creates a copy of the RegularPolygon rotated about a Point
  1501. """
  1502. self._rot += angle
  1503. def rotate(self, angle, pt=None):
  1504. """Override GeometryEntity.rotate to first rotate the RegularPolygon
  1505. about its center.
  1506. >>> from sympy import Point, RegularPolygon, pi
  1507. >>> t = RegularPolygon(Point(1, 0), 1, 3)
  1508. >>> t.vertices[0] # vertex on x-axis
  1509. Point2D(2, 0)
  1510. >>> t.rotate(pi/2).vertices[0] # vertex on y axis now
  1511. Point2D(0, 2)
  1512. See Also
  1513. ========
  1514. rotation
  1515. spin : Rotates a RegularPolygon in place
  1516. """
  1517. r = type(self)(*self.args) # need a copy or else changes are in-place
  1518. r._rot += angle
  1519. return GeometryEntity.rotate(r, angle, pt)
  1520. def scale(self, x=1, y=1, pt=None):
  1521. """Override GeometryEntity.scale since it is the radius that must be
  1522. scaled (if x == y) or else a new Polygon must be returned.
  1523. >>> from sympy import RegularPolygon
  1524. Symmetric scaling returns a RegularPolygon:
  1525. >>> RegularPolygon((0, 0), 1, 4).scale(2, 2)
  1526. RegularPolygon(Point2D(0, 0), 2, 4, 0)
  1527. Asymmetric scaling returns a kite as a Polygon:
  1528. >>> RegularPolygon((0, 0), 1, 4).scale(2, 1)
  1529. Polygon(Point2D(2, 0), Point2D(0, 1), Point2D(-2, 0), Point2D(0, -1))
  1530. """
  1531. if pt:
  1532. pt = Point(pt, dim=2)
  1533. return self.translate(*(-pt).args).scale(x, y).translate(*pt.args)
  1534. if x != y:
  1535. return Polygon(*self.vertices).scale(x, y)
  1536. c, r, n, rot = self.args
  1537. r *= x
  1538. return self.func(c, r, n, rot)
  1539. def reflect(self, line):
  1540. """Override GeometryEntity.reflect since this is not made of only
  1541. points.
  1542. Examples
  1543. ========
  1544. >>> from sympy import RegularPolygon, Line
  1545. >>> RegularPolygon((0, 0), 1, 4).reflect(Line((0, 1), slope=-2))
  1546. RegularPolygon(Point2D(4/5, 2/5), -1, 4, atan(4/3))
  1547. """
  1548. c, r, n, rot = self.args
  1549. v = self.vertices[0]
  1550. d = v - c
  1551. cc = c.reflect(line)
  1552. vv = v.reflect(line)
  1553. dd = vv - cc
  1554. # calculate rotation about the new center
  1555. # which will align the vertices
  1556. l1 = Ray((0, 0), dd)
  1557. l2 = Ray((0, 0), d)
  1558. ang = l1.closing_angle(l2)
  1559. rot += ang
  1560. # change sign of radius as point traversal is reversed
  1561. return self.func(cc, -r, n, rot)
  1562. @property
  1563. def vertices(self):
  1564. """The vertices of the RegularPolygon.
  1565. Returns
  1566. =======
  1567. vertices : list
  1568. Each vertex is a Point.
  1569. See Also
  1570. ========
  1571. sympy.geometry.point.Point
  1572. Examples
  1573. ========
  1574. >>> from sympy import RegularPolygon, Point
  1575. >>> rp = RegularPolygon(Point(0, 0), 5, 4)
  1576. >>> rp.vertices
  1577. [Point2D(5, 0), Point2D(0, 5), Point2D(-5, 0), Point2D(0, -5)]
  1578. """
  1579. c = self._center
  1580. r = abs(self._radius)
  1581. rot = self._rot
  1582. v = 2*S.Pi/self._n
  1583. return [Point(c.x + r*cos(k*v + rot), c.y + r*sin(k*v + rot))
  1584. for k in range(self._n)]
  1585. def __eq__(self, o):
  1586. if not isinstance(o, Polygon):
  1587. return False
  1588. elif not isinstance(o, RegularPolygon):
  1589. return Polygon.__eq__(o, self)
  1590. return self.args == o.args
  1591. def __hash__(self):
  1592. return super().__hash__()
  1593. class Triangle(Polygon):
  1594. """
  1595. A polygon with three vertices and three sides.
  1596. Parameters
  1597. ==========
  1598. points : sequence of Points
  1599. keyword: asa, sas, or sss to specify sides/angles of the triangle
  1600. Attributes
  1601. ==========
  1602. vertices
  1603. altitudes
  1604. orthocenter
  1605. circumcenter
  1606. circumradius
  1607. circumcircle
  1608. inradius
  1609. incircle
  1610. exradii
  1611. medians
  1612. medial
  1613. nine_point_circle
  1614. Raises
  1615. ======
  1616. GeometryError
  1617. If the number of vertices is not equal to three, or one of the vertices
  1618. is not a Point, or a valid keyword is not given.
  1619. See Also
  1620. ========
  1621. sympy.geometry.point.Point, Polygon
  1622. Examples
  1623. ========
  1624. >>> from sympy import Triangle, Point
  1625. >>> Triangle(Point(0, 0), Point(4, 0), Point(4, 3))
  1626. Triangle(Point2D(0, 0), Point2D(4, 0), Point2D(4, 3))
  1627. Keywords sss, sas, or asa can be used to give the desired
  1628. side lengths (in order) and interior angles (in degrees) that
  1629. define the triangle:
  1630. >>> Triangle(sss=(3, 4, 5))
  1631. Triangle(Point2D(0, 0), Point2D(3, 0), Point2D(3, 4))
  1632. >>> Triangle(asa=(30, 1, 30))
  1633. Triangle(Point2D(0, 0), Point2D(1, 0), Point2D(1/2, sqrt(3)/6))
  1634. >>> Triangle(sas=(1, 45, 2))
  1635. Triangle(Point2D(0, 0), Point2D(2, 0), Point2D(sqrt(2)/2, sqrt(2)/2))
  1636. """
  1637. def __new__(cls, *args, **kwargs):
  1638. if len(args) != 3:
  1639. if 'sss' in kwargs:
  1640. return _sss(*[simplify(a) for a in kwargs['sss']])
  1641. if 'asa' in kwargs:
  1642. return _asa(*[simplify(a) for a in kwargs['asa']])
  1643. if 'sas' in kwargs:
  1644. return _sas(*[simplify(a) for a in kwargs['sas']])
  1645. msg = "Triangle instantiates with three points or a valid keyword."
  1646. raise GeometryError(msg)
  1647. vertices = [Point(a, dim=2, **kwargs) for a in args]
  1648. # remove consecutive duplicates
  1649. nodup = []
  1650. for p in vertices:
  1651. if nodup and p == nodup[-1]:
  1652. continue
  1653. nodup.append(p)
  1654. if len(nodup) > 1 and nodup[-1] == nodup[0]:
  1655. nodup.pop() # last point was same as first
  1656. # remove collinear points
  1657. i = -3
  1658. while i < len(nodup) - 3 and len(nodup) > 2:
  1659. a, b, c = sorted(
  1660. [nodup[i], nodup[i + 1], nodup[i + 2]], key=default_sort_key)
  1661. if Point.is_collinear(a, b, c):
  1662. nodup[i] = a
  1663. nodup[i + 1] = None
  1664. nodup.pop(i + 1)
  1665. i += 1
  1666. vertices = list(filter(lambda x: x is not None, nodup))
  1667. if len(vertices) == 3:
  1668. return GeometryEntity.__new__(cls, *vertices, **kwargs)
  1669. elif len(vertices) == 2:
  1670. return Segment(*vertices, **kwargs)
  1671. else:
  1672. return Point(*vertices, **kwargs)
  1673. @property
  1674. def vertices(self):
  1675. """The triangle's vertices
  1676. Returns
  1677. =======
  1678. vertices : tuple
  1679. Each element in the tuple is a Point
  1680. See Also
  1681. ========
  1682. sympy.geometry.point.Point
  1683. Examples
  1684. ========
  1685. >>> from sympy import Triangle, Point
  1686. >>> t = Triangle(Point(0, 0), Point(4, 0), Point(4, 3))
  1687. >>> t.vertices
  1688. (Point2D(0, 0), Point2D(4, 0), Point2D(4, 3))
  1689. """
  1690. return self.args
  1691. def is_similar(t1, t2):
  1692. """Is another triangle similar to this one.
  1693. Two triangles are similar if one can be uniformly scaled to the other.
  1694. Parameters
  1695. ==========
  1696. other: Triangle
  1697. Returns
  1698. =======
  1699. is_similar : boolean
  1700. See Also
  1701. ========
  1702. sympy.geometry.entity.GeometryEntity.is_similar
  1703. Examples
  1704. ========
  1705. >>> from sympy import Triangle, Point
  1706. >>> t1 = Triangle(Point(0, 0), Point(4, 0), Point(4, 3))
  1707. >>> t2 = Triangle(Point(0, 0), Point(-4, 0), Point(-4, -3))
  1708. >>> t1.is_similar(t2)
  1709. True
  1710. >>> t2 = Triangle(Point(0, 0), Point(-4, 0), Point(-4, -4))
  1711. >>> t1.is_similar(t2)
  1712. False
  1713. """
  1714. if not isinstance(t2, Polygon):
  1715. return False
  1716. s1_1, s1_2, s1_3 = [side.length for side in t1.sides]
  1717. s2 = [side.length for side in t2.sides]
  1718. def _are_similar(u1, u2, u3, v1, v2, v3):
  1719. e1 = simplify(u1/v1)
  1720. e2 = simplify(u2/v2)
  1721. e3 = simplify(u3/v3)
  1722. return bool(e1 == e2) and bool(e2 == e3)
  1723. # There's only 6 permutations, so write them out
  1724. return _are_similar(s1_1, s1_2, s1_3, *s2) or \
  1725. _are_similar(s1_1, s1_3, s1_2, *s2) or \
  1726. _are_similar(s1_2, s1_1, s1_3, *s2) or \
  1727. _are_similar(s1_2, s1_3, s1_1, *s2) or \
  1728. _are_similar(s1_3, s1_1, s1_2, *s2) or \
  1729. _are_similar(s1_3, s1_2, s1_1, *s2)
  1730. def is_equilateral(self):
  1731. """Are all the sides the same length?
  1732. Returns
  1733. =======
  1734. is_equilateral : boolean
  1735. See Also
  1736. ========
  1737. sympy.geometry.entity.GeometryEntity.is_similar, RegularPolygon
  1738. is_isosceles, is_right, is_scalene
  1739. Examples
  1740. ========
  1741. >>> from sympy import Triangle, Point
  1742. >>> t1 = Triangle(Point(0, 0), Point(4, 0), Point(4, 3))
  1743. >>> t1.is_equilateral()
  1744. False
  1745. >>> from sympy import sqrt
  1746. >>> t2 = Triangle(Point(0, 0), Point(10, 0), Point(5, 5*sqrt(3)))
  1747. >>> t2.is_equilateral()
  1748. True
  1749. """
  1750. return not has_variety(s.length for s in self.sides)
  1751. def is_isosceles(self):
  1752. """Are two or more of the sides the same length?
  1753. Returns
  1754. =======
  1755. is_isosceles : boolean
  1756. See Also
  1757. ========
  1758. is_equilateral, is_right, is_scalene
  1759. Examples
  1760. ========
  1761. >>> from sympy import Triangle, Point
  1762. >>> t1 = Triangle(Point(0, 0), Point(4, 0), Point(2, 4))
  1763. >>> t1.is_isosceles()
  1764. True
  1765. """
  1766. return has_dups(s.length for s in self.sides)
  1767. def is_scalene(self):
  1768. """Are all the sides of the triangle of different lengths?
  1769. Returns
  1770. =======
  1771. is_scalene : boolean
  1772. See Also
  1773. ========
  1774. is_equilateral, is_isosceles, is_right
  1775. Examples
  1776. ========
  1777. >>> from sympy import Triangle, Point
  1778. >>> t1 = Triangle(Point(0, 0), Point(4, 0), Point(1, 4))
  1779. >>> t1.is_scalene()
  1780. True
  1781. """
  1782. return not has_dups(s.length for s in self.sides)
  1783. def is_right(self):
  1784. """Is the triangle right-angled.
  1785. Returns
  1786. =======
  1787. is_right : boolean
  1788. See Also
  1789. ========
  1790. sympy.geometry.line.LinearEntity.is_perpendicular
  1791. is_equilateral, is_isosceles, is_scalene
  1792. Examples
  1793. ========
  1794. >>> from sympy import Triangle, Point
  1795. >>> t1 = Triangle(Point(0, 0), Point(4, 0), Point(4, 3))
  1796. >>> t1.is_right()
  1797. True
  1798. """
  1799. s = self.sides
  1800. return Segment.is_perpendicular(s[0], s[1]) or \
  1801. Segment.is_perpendicular(s[1], s[2]) or \
  1802. Segment.is_perpendicular(s[0], s[2])
  1803. @property
  1804. def altitudes(self):
  1805. """The altitudes of the triangle.
  1806. An altitude of a triangle is a segment through a vertex,
  1807. perpendicular to the opposite side, with length being the
  1808. height of the vertex measured from the line containing the side.
  1809. Returns
  1810. =======
  1811. altitudes : dict
  1812. The dictionary consists of keys which are vertices and values
  1813. which are Segments.
  1814. See Also
  1815. ========
  1816. sympy.geometry.point.Point, sympy.geometry.line.Segment.length
  1817. Examples
  1818. ========
  1819. >>> from sympy import Point, Triangle
  1820. >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1)
  1821. >>> t = Triangle(p1, p2, p3)
  1822. >>> t.altitudes[p1]
  1823. Segment2D(Point2D(0, 0), Point2D(1/2, 1/2))
  1824. """
  1825. s = self.sides
  1826. v = self.vertices
  1827. return {v[0]: s[1].perpendicular_segment(v[0]),
  1828. v[1]: s[2].perpendicular_segment(v[1]),
  1829. v[2]: s[0].perpendicular_segment(v[2])}
  1830. @property
  1831. def orthocenter(self):
  1832. """The orthocenter of the triangle.
  1833. The orthocenter is the intersection of the altitudes of a triangle.
  1834. It may lie inside, outside or on the triangle.
  1835. Returns
  1836. =======
  1837. orthocenter : Point
  1838. See Also
  1839. ========
  1840. sympy.geometry.point.Point
  1841. Examples
  1842. ========
  1843. >>> from sympy import Point, Triangle
  1844. >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1)
  1845. >>> t = Triangle(p1, p2, p3)
  1846. >>> t.orthocenter
  1847. Point2D(0, 0)
  1848. """
  1849. a = self.altitudes
  1850. v = self.vertices
  1851. return Line(a[v[0]]).intersection(Line(a[v[1]]))[0]
  1852. @property
  1853. def circumcenter(self):
  1854. """The circumcenter of the triangle
  1855. The circumcenter is the center of the circumcircle.
  1856. Returns
  1857. =======
  1858. circumcenter : Point
  1859. See Also
  1860. ========
  1861. sympy.geometry.point.Point
  1862. Examples
  1863. ========
  1864. >>> from sympy import Point, Triangle
  1865. >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1)
  1866. >>> t = Triangle(p1, p2, p3)
  1867. >>> t.circumcenter
  1868. Point2D(1/2, 1/2)
  1869. """
  1870. a, b, c = [x.perpendicular_bisector() for x in self.sides]
  1871. if not a.intersection(b):
  1872. print(a,b,a.intersection(b))
  1873. return a.intersection(b)[0]
  1874. @property
  1875. def circumradius(self):
  1876. """The radius of the circumcircle of the triangle.
  1877. Returns
  1878. =======
  1879. circumradius : number of Basic instance
  1880. See Also
  1881. ========
  1882. sympy.geometry.ellipse.Circle.radius
  1883. Examples
  1884. ========
  1885. >>> from sympy import Symbol
  1886. >>> from sympy import Point, Triangle
  1887. >>> a = Symbol('a')
  1888. >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, a)
  1889. >>> t = Triangle(p1, p2, p3)
  1890. >>> t.circumradius
  1891. sqrt(a**2/4 + 1/4)
  1892. """
  1893. return Point.distance(self.circumcenter, self.vertices[0])
  1894. @property
  1895. def circumcircle(self):
  1896. """The circle which passes through the three vertices of the triangle.
  1897. Returns
  1898. =======
  1899. circumcircle : Circle
  1900. See Also
  1901. ========
  1902. sympy.geometry.ellipse.Circle
  1903. Examples
  1904. ========
  1905. >>> from sympy import Point, Triangle
  1906. >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1)
  1907. >>> t = Triangle(p1, p2, p3)
  1908. >>> t.circumcircle
  1909. Circle(Point2D(1/2, 1/2), sqrt(2)/2)
  1910. """
  1911. return Circle(self.circumcenter, self.circumradius)
  1912. def bisectors(self):
  1913. """The angle bisectors of the triangle.
  1914. An angle bisector of a triangle is a straight line through a vertex
  1915. which cuts the corresponding angle in half.
  1916. Returns
  1917. =======
  1918. bisectors : dict
  1919. Each key is a vertex (Point) and each value is the corresponding
  1920. bisector (Segment).
  1921. See Also
  1922. ========
  1923. sympy.geometry.point.Point, sympy.geometry.line.Segment
  1924. Examples
  1925. ========
  1926. >>> from sympy import Point, Triangle, Segment
  1927. >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1)
  1928. >>> t = Triangle(p1, p2, p3)
  1929. >>> from sympy import sqrt
  1930. >>> t.bisectors()[p2] == Segment(Point(1, 0), Point(0, sqrt(2) - 1))
  1931. True
  1932. """
  1933. # use lines containing sides so containment check during
  1934. # intersection calculation can be avoided, thus reducing
  1935. # the processing time for calculating the bisectors
  1936. s = [Line(l) for l in self.sides]
  1937. v = self.vertices
  1938. c = self.incenter
  1939. l1 = Segment(v[0], Line(v[0], c).intersection(s[1])[0])
  1940. l2 = Segment(v[1], Line(v[1], c).intersection(s[2])[0])
  1941. l3 = Segment(v[2], Line(v[2], c).intersection(s[0])[0])
  1942. return {v[0]: l1, v[1]: l2, v[2]: l3}
  1943. @property
  1944. def incenter(self):
  1945. """The center of the incircle.
  1946. The incircle is the circle which lies inside the triangle and touches
  1947. all three sides.
  1948. Returns
  1949. =======
  1950. incenter : Point
  1951. See Also
  1952. ========
  1953. incircle, sympy.geometry.point.Point
  1954. Examples
  1955. ========
  1956. >>> from sympy import Point, Triangle
  1957. >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1)
  1958. >>> t = Triangle(p1, p2, p3)
  1959. >>> t.incenter
  1960. Point2D(1 - sqrt(2)/2, 1 - sqrt(2)/2)
  1961. """
  1962. s = self.sides
  1963. l = Matrix([s[i].length for i in [1, 2, 0]])
  1964. p = sum(l)
  1965. v = self.vertices
  1966. x = simplify(l.dot(Matrix([vi.x for vi in v]))/p)
  1967. y = simplify(l.dot(Matrix([vi.y for vi in v]))/p)
  1968. return Point(x, y)
  1969. @property
  1970. def inradius(self):
  1971. """The radius of the incircle.
  1972. Returns
  1973. =======
  1974. inradius : number of Basic instance
  1975. See Also
  1976. ========
  1977. incircle, sympy.geometry.ellipse.Circle.radius
  1978. Examples
  1979. ========
  1980. >>> from sympy import Point, Triangle
  1981. >>> p1, p2, p3 = Point(0, 0), Point(4, 0), Point(0, 3)
  1982. >>> t = Triangle(p1, p2, p3)
  1983. >>> t.inradius
  1984. 1
  1985. """
  1986. return simplify(2 * self.area / self.perimeter)
  1987. @property
  1988. def incircle(self):
  1989. """The incircle of the triangle.
  1990. The incircle is the circle which lies inside the triangle and touches
  1991. all three sides.
  1992. Returns
  1993. =======
  1994. incircle : Circle
  1995. See Also
  1996. ========
  1997. sympy.geometry.ellipse.Circle
  1998. Examples
  1999. ========
  2000. >>> from sympy import Point, Triangle
  2001. >>> p1, p2, p3 = Point(0, 0), Point(2, 0), Point(0, 2)
  2002. >>> t = Triangle(p1, p2, p3)
  2003. >>> t.incircle
  2004. Circle(Point2D(2 - sqrt(2), 2 - sqrt(2)), 2 - sqrt(2))
  2005. """
  2006. return Circle(self.incenter, self.inradius)
  2007. @property
  2008. def exradii(self):
  2009. """The radius of excircles of a triangle.
  2010. An excircle of the triangle is a circle lying outside the triangle,
  2011. tangent to one of its sides and tangent to the extensions of the
  2012. other two.
  2013. Returns
  2014. =======
  2015. exradii : dict
  2016. See Also
  2017. ========
  2018. sympy.geometry.polygon.Triangle.inradius
  2019. Examples
  2020. ========
  2021. The exradius touches the side of the triangle to which it is keyed, e.g.
  2022. the exradius touching side 2 is:
  2023. >>> from sympy import Point, Triangle
  2024. >>> p1, p2, p3 = Point(0, 0), Point(6, 0), Point(0, 2)
  2025. >>> t = Triangle(p1, p2, p3)
  2026. >>> t.exradii[t.sides[2]]
  2027. -2 + sqrt(10)
  2028. References
  2029. ==========
  2030. .. [1] http://mathworld.wolfram.com/Exradius.html
  2031. .. [2] http://mathworld.wolfram.com/Excircles.html
  2032. """
  2033. side = self.sides
  2034. a = side[0].length
  2035. b = side[1].length
  2036. c = side[2].length
  2037. s = (a+b+c)/2
  2038. area = self.area
  2039. exradii = {self.sides[0]: simplify(area/(s-a)),
  2040. self.sides[1]: simplify(area/(s-b)),
  2041. self.sides[2]: simplify(area/(s-c))}
  2042. return exradii
  2043. @property
  2044. def excenters(self):
  2045. """Excenters of the triangle.
  2046. An excenter is the center of a circle that is tangent to a side of the
  2047. triangle and the extensions of the other two sides.
  2048. Returns
  2049. =======
  2050. excenters : dict
  2051. Examples
  2052. ========
  2053. The excenters are keyed to the side of the triangle to which their corresponding
  2054. excircle is tangent: The center is keyed, e.g. the excenter of a circle touching
  2055. side 0 is:
  2056. >>> from sympy import Point, Triangle
  2057. >>> p1, p2, p3 = Point(0, 0), Point(6, 0), Point(0, 2)
  2058. >>> t = Triangle(p1, p2, p3)
  2059. >>> t.excenters[t.sides[0]]
  2060. Point2D(12*sqrt(10), 2/3 + sqrt(10)/3)
  2061. See Also
  2062. ========
  2063. sympy.geometry.polygon.Triangle.exradii
  2064. References
  2065. ==========
  2066. .. [1] http://mathworld.wolfram.com/Excircles.html
  2067. """
  2068. s = self.sides
  2069. v = self.vertices
  2070. a = s[0].length
  2071. b = s[1].length
  2072. c = s[2].length
  2073. x = [v[0].x, v[1].x, v[2].x]
  2074. y = [v[0].y, v[1].y, v[2].y]
  2075. exc_coords = {
  2076. "x1": simplify(-a*x[0]+b*x[1]+c*x[2]/(-a+b+c)),
  2077. "x2": simplify(a*x[0]-b*x[1]+c*x[2]/(a-b+c)),
  2078. "x3": simplify(a*x[0]+b*x[1]-c*x[2]/(a+b-c)),
  2079. "y1": simplify(-a*y[0]+b*y[1]+c*y[2]/(-a+b+c)),
  2080. "y2": simplify(a*y[0]-b*y[1]+c*y[2]/(a-b+c)),
  2081. "y3": simplify(a*y[0]+b*y[1]-c*y[2]/(a+b-c))
  2082. }
  2083. excenters = {
  2084. s[0]: Point(exc_coords["x1"], exc_coords["y1"]),
  2085. s[1]: Point(exc_coords["x2"], exc_coords["y2"]),
  2086. s[2]: Point(exc_coords["x3"], exc_coords["y3"])
  2087. }
  2088. return excenters
  2089. @property
  2090. def medians(self):
  2091. """The medians of the triangle.
  2092. A median of a triangle is a straight line through a vertex and the
  2093. midpoint of the opposite side, and divides the triangle into two
  2094. equal areas.
  2095. Returns
  2096. =======
  2097. medians : dict
  2098. Each key is a vertex (Point) and each value is the median (Segment)
  2099. at that point.
  2100. See Also
  2101. ========
  2102. sympy.geometry.point.Point.midpoint, sympy.geometry.line.Segment.midpoint
  2103. Examples
  2104. ========
  2105. >>> from sympy import Point, Triangle
  2106. >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1)
  2107. >>> t = Triangle(p1, p2, p3)
  2108. >>> t.medians[p1]
  2109. Segment2D(Point2D(0, 0), Point2D(1/2, 1/2))
  2110. """
  2111. s = self.sides
  2112. v = self.vertices
  2113. return {v[0]: Segment(v[0], s[1].midpoint),
  2114. v[1]: Segment(v[1], s[2].midpoint),
  2115. v[2]: Segment(v[2], s[0].midpoint)}
  2116. @property
  2117. def medial(self):
  2118. """The medial triangle of the triangle.
  2119. The triangle which is formed from the midpoints of the three sides.
  2120. Returns
  2121. =======
  2122. medial : Triangle
  2123. See Also
  2124. ========
  2125. sympy.geometry.line.Segment.midpoint
  2126. Examples
  2127. ========
  2128. >>> from sympy import Point, Triangle
  2129. >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1)
  2130. >>> t = Triangle(p1, p2, p3)
  2131. >>> t.medial
  2132. Triangle(Point2D(1/2, 0), Point2D(1/2, 1/2), Point2D(0, 1/2))
  2133. """
  2134. s = self.sides
  2135. return Triangle(s[0].midpoint, s[1].midpoint, s[2].midpoint)
  2136. @property
  2137. def nine_point_circle(self):
  2138. """The nine-point circle of the triangle.
  2139. Nine-point circle is the circumcircle of the medial triangle, which
  2140. passes through the feet of altitudes and the middle points of segments
  2141. connecting the vertices and the orthocenter.
  2142. Returns
  2143. =======
  2144. nine_point_circle : Circle
  2145. See also
  2146. ========
  2147. sympy.geometry.line.Segment.midpoint
  2148. sympy.geometry.polygon.Triangle.medial
  2149. sympy.geometry.polygon.Triangle.orthocenter
  2150. Examples
  2151. ========
  2152. >>> from sympy import Point, Triangle
  2153. >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1)
  2154. >>> t = Triangle(p1, p2, p3)
  2155. >>> t.nine_point_circle
  2156. Circle(Point2D(1/4, 1/4), sqrt(2)/4)
  2157. """
  2158. return Circle(*self.medial.vertices)
  2159. @property
  2160. def eulerline(self):
  2161. """The Euler line of the triangle.
  2162. The line which passes through circumcenter, centroid and orthocenter.
  2163. Returns
  2164. =======
  2165. eulerline : Line (or Point for equilateral triangles in which case all
  2166. centers coincide)
  2167. Examples
  2168. ========
  2169. >>> from sympy import Point, Triangle
  2170. >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1)
  2171. >>> t = Triangle(p1, p2, p3)
  2172. >>> t.eulerline
  2173. Line2D(Point2D(0, 0), Point2D(1/2, 1/2))
  2174. """
  2175. if self.is_equilateral():
  2176. return self.orthocenter
  2177. return Line(self.orthocenter, self.circumcenter)
  2178. def rad(d):
  2179. """Return the radian value for the given degrees (pi = 180 degrees)."""
  2180. return d*pi/180
  2181. def deg(r):
  2182. """Return the degree value for the given radians (pi = 180 degrees)."""
  2183. return r/pi*180
  2184. def _slope(d):
  2185. rv = tan(rad(d))
  2186. return rv
  2187. def _asa(d1, l, d2):
  2188. """Return triangle having side with length l on the x-axis."""
  2189. xy = Line((0, 0), slope=_slope(d1)).intersection(
  2190. Line((l, 0), slope=_slope(180 - d2)))[0]
  2191. return Triangle((0, 0), (l, 0), xy)
  2192. def _sss(l1, l2, l3):
  2193. """Return triangle having side of length l1 on the x-axis."""
  2194. c1 = Circle((0, 0), l3)
  2195. c2 = Circle((l1, 0), l2)
  2196. inter = [a for a in c1.intersection(c2) if a.y.is_nonnegative]
  2197. if not inter:
  2198. return None
  2199. pt = inter[0]
  2200. return Triangle((0, 0), (l1, 0), pt)
  2201. def _sas(l1, d, l2):
  2202. """Return triangle having side with length l2 on the x-axis."""
  2203. p1 = Point(0, 0)
  2204. p2 = Point(l2, 0)
  2205. p3 = Point(cos(rad(d))*l1, sin(rad(d))*l1)
  2206. return Triangle(p1, p2, p3)