Coverage for /usr/lib/python3/dist-packages/sympy/geometry/polygon.py: 19%
786 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
1from sympy.core import Expr, S, oo, pi, sympify
2from sympy.core.evalf import N
3from sympy.core.sorting import default_sort_key, ordered
4from sympy.core.symbol import _symbol, Dummy, Symbol
5from sympy.functions.elementary.complexes import sign
6from sympy.functions.elementary.piecewise import Piecewise
7from sympy.functions.elementary.trigonometric import cos, sin, tan
8from .ellipse import Circle
9from .entity import GeometryEntity, GeometrySet
10from .exceptions import GeometryError
11from .line import Line, Segment, Ray
12from .point import Point
13from sympy.logic import And
14from sympy.matrices import Matrix
15from sympy.simplify.simplify import simplify
16from sympy.solvers.solvers import solve
17from sympy.utilities.iterables import has_dups, has_variety, uniq, rotate_left, least_rotation
18from sympy.utilities.misc import as_int, func_name
20from mpmath.libmp.libmpf import prec_to_dps
22import warnings
25x, y, T = [Dummy('polygon_dummy', real=True) for i in range(3)]
28class Polygon(GeometrySet):
29 """A two-dimensional polygon.
31 A simple polygon in space. Can be constructed from a sequence of points
32 or from a center, radius, number of sides and rotation angle.
34 Parameters
35 ==========
37 vertices
38 A sequence of points.
40 n : int, optional
41 If $> 0$, an n-sided RegularPolygon is created.
42 Default value is $0$.
44 Attributes
45 ==========
47 area
48 angles
49 perimeter
50 vertices
51 centroid
52 sides
54 Raises
55 ======
57 GeometryError
58 If all parameters are not Points.
60 See Also
61 ========
63 sympy.geometry.point.Point, sympy.geometry.line.Segment, Triangle
65 Notes
66 =====
68 Polygons are treated as closed paths rather than 2D areas so
69 some calculations can be be negative or positive (e.g., area)
70 based on the orientation of the points.
72 Any consecutive identical points are reduced to a single point
73 and any points collinear and between two points will be removed
74 unless they are needed to define an explicit intersection (see examples).
76 A Triangle, Segment or Point will be returned when there are 3 or
77 fewer points provided.
79 Examples
80 ========
82 >>> from sympy import Polygon, pi
83 >>> p1, p2, p3, p4, p5 = [(0, 0), (1, 0), (5, 1), (0, 1), (3, 0)]
84 >>> Polygon(p1, p2, p3, p4)
85 Polygon(Point2D(0, 0), Point2D(1, 0), Point2D(5, 1), Point2D(0, 1))
86 >>> Polygon(p1, p2)
87 Segment2D(Point2D(0, 0), Point2D(1, 0))
88 >>> Polygon(p1, p2, p5)
89 Segment2D(Point2D(0, 0), Point2D(3, 0))
91 The area of a polygon is calculated as positive when vertices are
92 traversed in a ccw direction. When the sides of a polygon cross the
93 area will have positive and negative contributions. The following
94 defines a Z shape where the bottom right connects back to the top
95 left.
97 >>> Polygon((0, 2), (2, 2), (0, 0), (2, 0)).area
98 0
100 When the keyword `n` is used to define the number of sides of the
101 Polygon then a RegularPolygon is created and the other arguments are
102 interpreted as center, radius and rotation. The unrotated RegularPolygon
103 will always have a vertex at Point(r, 0) where `r` is the radius of the
104 circle that circumscribes the RegularPolygon. Its method `spin` can be
105 used to increment that angle.
107 >>> p = Polygon((0,0), 1, n=3)
108 >>> p
109 RegularPolygon(Point2D(0, 0), 1, 3, 0)
110 >>> p.vertices[0]
111 Point2D(1, 0)
112 >>> p.args[0]
113 Point2D(0, 0)
114 >>> p.spin(pi/2)
115 >>> p.vertices[0]
116 Point2D(0, 1)
118 """
120 __slots__ = ()
122 def __new__(cls, *args, n = 0, **kwargs):
123 if n:
124 args = list(args)
125 # return a virtual polygon with n sides
126 if len(args) == 2: # center, radius
127 args.append(n)
128 elif len(args) == 3: # center, radius, rotation
129 args.insert(2, n)
130 return RegularPolygon(*args, **kwargs)
132 vertices = [Point(a, dim=2, **kwargs) for a in args]
134 # remove consecutive duplicates
135 nodup = []
136 for p in vertices:
137 if nodup and p == nodup[-1]:
138 continue
139 nodup.append(p)
140 if len(nodup) > 1 and nodup[-1] == nodup[0]:
141 nodup.pop() # last point was same as first
143 # remove collinear points
144 i = -3
145 while i < len(nodup) - 3 and len(nodup) > 2:
146 a, b, c = nodup[i], nodup[i + 1], nodup[i + 2]
147 if Point.is_collinear(a, b, c):
148 nodup.pop(i + 1)
149 if a == c:
150 nodup.pop(i)
151 else:
152 i += 1
154 vertices = list(nodup)
156 if len(vertices) > 3:
157 return GeometryEntity.__new__(cls, *vertices, **kwargs)
158 elif len(vertices) == 3:
159 return Triangle(*vertices, **kwargs)
160 elif len(vertices) == 2:
161 return Segment(*vertices, **kwargs)
162 else:
163 return Point(*vertices, **kwargs)
165 @property
166 def area(self):
167 """
168 The area of the polygon.
170 Notes
171 =====
173 The area calculation can be positive or negative based on the
174 orientation of the points. If any side of the polygon crosses
175 any other side, there will be areas having opposite signs.
177 See Also
178 ========
180 sympy.geometry.ellipse.Ellipse.area
182 Examples
183 ========
185 >>> from sympy import Point, Polygon
186 >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)])
187 >>> poly = Polygon(p1, p2, p3, p4)
188 >>> poly.area
189 3
191 In the Z shaped polygon (with the lower right connecting back
192 to the upper left) the areas cancel out:
194 >>> Z = Polygon((0, 1), (1, 1), (0, 0), (1, 0))
195 >>> Z.area
196 0
198 In the M shaped polygon, areas do not cancel because no side
199 crosses any other (though there is a point of contact).
201 >>> M = Polygon((0, 0), (0, 1), (2, 0), (3, 1), (3, 0))
202 >>> M.area
203 -3/2
205 """
206 area = 0
207 args = self.args
208 for i in range(len(args)):
209 x1, y1 = args[i - 1].args
210 x2, y2 = args[i].args
211 area += x1*y2 - x2*y1
212 return simplify(area) / 2
214 @staticmethod
215 def _isright(a, b, c):
216 """Return True/False for cw/ccw orientation.
218 Examples
219 ========
221 >>> from sympy import Point, Polygon
222 >>> a, b, c = [Point(i) for i in [(0, 0), (1, 1), (1, 0)]]
223 >>> Polygon._isright(a, b, c)
224 True
225 >>> Polygon._isright(a, c, b)
226 False
227 """
228 ba = b - a
229 ca = c - a
230 t_area = simplify(ba.x*ca.y - ca.x*ba.y)
231 res = t_area.is_nonpositive
232 if res is None:
233 raise ValueError("Can't determine orientation")
234 return res
236 @property
237 def angles(self):
238 """The internal angle at each vertex.
240 Returns
241 =======
243 angles : dict
244 A dictionary where each key is a vertex and each value is the
245 internal angle at that vertex. The vertices are represented as
246 Points.
248 See Also
249 ========
251 sympy.geometry.point.Point, sympy.geometry.line.LinearEntity.angle_between
253 Examples
254 ========
256 >>> from sympy import Point, Polygon
257 >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)])
258 >>> poly = Polygon(p1, p2, p3, p4)
259 >>> poly.angles[p1]
260 pi/2
261 >>> poly.angles[p2]
262 acos(-4*sqrt(17)/17)
264 """
266 # Determine orientation of points
267 args = self.vertices
268 cw = self._isright(args[-1], args[0], args[1])
270 ret = {}
271 for i in range(len(args)):
272 a, b, c = args[i - 2], args[i - 1], args[i]
273 ang = Ray(b, a).angle_between(Ray(b, c))
274 if cw ^ self._isright(a, b, c):
275 ret[b] = 2*S.Pi - ang
276 else:
277 ret[b] = ang
278 return ret
280 @property
281 def ambient_dimension(self):
282 return self.vertices[0].ambient_dimension
284 @property
285 def perimeter(self):
286 """The perimeter of the polygon.
288 Returns
289 =======
291 perimeter : number or Basic instance
293 See Also
294 ========
296 sympy.geometry.line.Segment.length
298 Examples
299 ========
301 >>> from sympy import Point, Polygon
302 >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)])
303 >>> poly = Polygon(p1, p2, p3, p4)
304 >>> poly.perimeter
305 sqrt(17) + 7
306 """
307 p = 0
308 args = self.vertices
309 for i in range(len(args)):
310 p += args[i - 1].distance(args[i])
311 return simplify(p)
313 @property
314 def vertices(self):
315 """The vertices of the polygon.
317 Returns
318 =======
320 vertices : list of Points
322 Notes
323 =====
325 When iterating over the vertices, it is more efficient to index self
326 rather than to request the vertices and index them. Only use the
327 vertices when you want to process all of them at once. This is even
328 more important with RegularPolygons that calculate each vertex.
330 See Also
331 ========
333 sympy.geometry.point.Point
335 Examples
336 ========
338 >>> from sympy import Point, Polygon
339 >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)])
340 >>> poly = Polygon(p1, p2, p3, p4)
341 >>> poly.vertices
342 [Point2D(0, 0), Point2D(1, 0), Point2D(5, 1), Point2D(0, 1)]
343 >>> poly.vertices[0]
344 Point2D(0, 0)
346 """
347 return list(self.args)
349 @property
350 def centroid(self):
351 """The centroid of the polygon.
353 Returns
354 =======
356 centroid : Point
358 See Also
359 ========
361 sympy.geometry.point.Point, sympy.geometry.util.centroid
363 Examples
364 ========
366 >>> from sympy import Point, Polygon
367 >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)])
368 >>> poly = Polygon(p1, p2, p3, p4)
369 >>> poly.centroid
370 Point2D(31/18, 11/18)
372 """
373 A = 1/(6*self.area)
374 cx, cy = 0, 0
375 args = self.args
376 for i in range(len(args)):
377 x1, y1 = args[i - 1].args
378 x2, y2 = args[i].args
379 v = x1*y2 - x2*y1
380 cx += v*(x1 + x2)
381 cy += v*(y1 + y2)
382 return Point(simplify(A*cx), simplify(A*cy))
385 def second_moment_of_area(self, point=None):
386 """Returns the second moment and product moment of area of a two dimensional polygon.
388 Parameters
389 ==========
391 point : Point, two-tuple of sympifyable objects, or None(default=None)
392 point is the point about which second moment of area is to be found.
393 If "point=None" it will be calculated about the axis passing through the
394 centroid of the polygon.
396 Returns
397 =======
399 I_xx, I_yy, I_xy : number or SymPy expression
400 I_xx, I_yy are second moment of area of a two dimensional polygon.
401 I_xy is product moment of area of a two dimensional polygon.
403 Examples
404 ========
406 >>> from sympy import Polygon, symbols
407 >>> a, b = symbols('a, b')
408 >>> p1, p2, p3, p4, p5 = [(0, 0), (a, 0), (a, b), (0, b), (a/3, b/3)]
409 >>> rectangle = Polygon(p1, p2, p3, p4)
410 >>> rectangle.second_moment_of_area()
411 (a*b**3/12, a**3*b/12, 0)
412 >>> rectangle.second_moment_of_area(p5)
413 (a*b**3/9, a**3*b/9, a**2*b**2/36)
415 References
416 ==========
418 .. [1] https://en.wikipedia.org/wiki/Second_moment_of_area
420 """
422 I_xx, I_yy, I_xy = 0, 0, 0
423 args = self.vertices
424 for i in range(len(args)):
425 x1, y1 = args[i-1].args
426 x2, y2 = args[i].args
427 v = x1*y2 - x2*y1
428 I_xx += (y1**2 + y1*y2 + y2**2)*v
429 I_yy += (x1**2 + x1*x2 + x2**2)*v
430 I_xy += (x1*y2 + 2*x1*y1 + 2*x2*y2 + x2*y1)*v
431 A = self.area
432 c_x = self.centroid[0]
433 c_y = self.centroid[1]
434 # parallel axis theorem
435 I_xx_c = (I_xx/12) - (A*(c_y**2))
436 I_yy_c = (I_yy/12) - (A*(c_x**2))
437 I_xy_c = (I_xy/24) - (A*(c_x*c_y))
438 if point is None:
439 return I_xx_c, I_yy_c, I_xy_c
441 I_xx = (I_xx_c + A*((point[1]-c_y)**2))
442 I_yy = (I_yy_c + A*((point[0]-c_x)**2))
443 I_xy = (I_xy_c + A*((point[0]-c_x)*(point[1]-c_y)))
445 return I_xx, I_yy, I_xy
448 def first_moment_of_area(self, point=None):
449 """
450 Returns the first moment of area of a two-dimensional polygon with
451 respect to a certain point of interest.
453 First moment of area is a measure of the distribution of the area
454 of a polygon in relation to an axis. The first moment of area of
455 the entire polygon about its own centroid is always zero. Therefore,
456 here it is calculated for an area, above or below a certain point
457 of interest, that makes up a smaller portion of the polygon. This
458 area is bounded by the point of interest and the extreme end
459 (top or bottom) of the polygon. The first moment for this area is
460 is then determined about the centroidal axis of the initial polygon.
462 References
463 ==========
465 .. [1] https://skyciv.com/docs/tutorials/section-tutorials/calculating-the-statical-or-first-moment-of-area-of-beam-sections/?cc=BMD
466 .. [2] https://mechanicalc.com/reference/cross-sections
468 Parameters
469 ==========
471 point: Point, two-tuple of sympifyable objects, or None (default=None)
472 point is the point above or below which the area of interest lies
473 If ``point=None`` then the centroid acts as the point of interest.
475 Returns
476 =======
478 Q_x, Q_y: number or SymPy expressions
479 Q_x is the first moment of area about the x-axis
480 Q_y is the first moment of area about the y-axis
481 A negative sign indicates that the section modulus is
482 determined for a section below (or left of) the centroidal axis
484 Examples
485 ========
487 >>> from sympy import Point, Polygon
488 >>> a, b = 50, 10
489 >>> p1, p2, p3, p4 = [(0, b), (0, 0), (a, 0), (a, b)]
490 >>> p = Polygon(p1, p2, p3, p4)
491 >>> p.first_moment_of_area()
492 (625, 3125)
493 >>> p.first_moment_of_area(point=Point(30, 7))
494 (525, 3000)
495 """
496 if point:
497 xc, yc = self.centroid
498 else:
499 point = self.centroid
500 xc, yc = point
502 h_line = Line(point, slope=0)
503 v_line = Line(point, slope=S.Infinity)
505 h_poly = self.cut_section(h_line)
506 v_poly = self.cut_section(v_line)
508 poly_1 = h_poly[0] if h_poly[0].area <= h_poly[1].area else h_poly[1]
509 poly_2 = v_poly[0] if v_poly[0].area <= v_poly[1].area else v_poly[1]
511 Q_x = (poly_1.centroid.y - yc)*poly_1.area
512 Q_y = (poly_2.centroid.x - xc)*poly_2.area
514 return Q_x, Q_y
517 def polar_second_moment_of_area(self):
518 """Returns the polar modulus of a two-dimensional polygon
520 It is a constituent of the second moment of area, linked through
521 the perpendicular axis theorem. While the planar second moment of
522 area describes an object's resistance to deflection (bending) when
523 subjected to a force applied to a plane parallel to the central
524 axis, the polar second moment of area describes an object's
525 resistance to deflection when subjected to a moment applied in a
526 plane perpendicular to the object's central axis (i.e. parallel to
527 the cross-section)
529 Examples
530 ========
532 >>> from sympy import Polygon, symbols
533 >>> a, b = symbols('a, b')
534 >>> rectangle = Polygon((0, 0), (a, 0), (a, b), (0, b))
535 >>> rectangle.polar_second_moment_of_area()
536 a**3*b/12 + a*b**3/12
538 References
539 ==========
541 .. [1] https://en.wikipedia.org/wiki/Polar_moment_of_inertia
543 """
544 second_moment = self.second_moment_of_area()
545 return second_moment[0] + second_moment[1]
548 def section_modulus(self, point=None):
549 """Returns a tuple with the section modulus of a two-dimensional
550 polygon.
552 Section modulus is a geometric property of a polygon defined as the
553 ratio of second moment of area to the distance of the extreme end of
554 the polygon from the centroidal axis.
556 Parameters
557 ==========
559 point : Point, two-tuple of sympifyable objects, or None(default=None)
560 point is the point at which section modulus is to be found.
561 If "point=None" it will be calculated for the point farthest from the
562 centroidal axis of the polygon.
564 Returns
565 =======
567 S_x, S_y: numbers or SymPy expressions
568 S_x is the section modulus with respect to the x-axis
569 S_y is the section modulus with respect to the y-axis
570 A negative sign indicates that the section modulus is
571 determined for a point below the centroidal axis
573 Examples
574 ========
576 >>> from sympy import symbols, Polygon, Point
577 >>> a, b = symbols('a, b', positive=True)
578 >>> rectangle = Polygon((0, 0), (a, 0), (a, b), (0, b))
579 >>> rectangle.section_modulus()
580 (a*b**2/6, a**2*b/6)
581 >>> rectangle.section_modulus(Point(a/4, b/4))
582 (-a*b**2/3, -a**2*b/3)
584 References
585 ==========
587 .. [1] https://en.wikipedia.org/wiki/Section_modulus
589 """
590 x_c, y_c = self.centroid
591 if point is None:
592 # taking x and y as maximum distances from centroid
593 x_min, y_min, x_max, y_max = self.bounds
594 y = max(y_c - y_min, y_max - y_c)
595 x = max(x_c - x_min, x_max - x_c)
596 else:
597 # taking x and y as distances of the given point from the centroid
598 y = point.y - y_c
599 x = point.x - x_c
601 second_moment= self.second_moment_of_area()
602 S_x = second_moment[0]/y
603 S_y = second_moment[1]/x
605 return S_x, S_y
608 @property
609 def sides(self):
610 """The directed line segments that form the sides of the polygon.
612 Returns
613 =======
615 sides : list of sides
616 Each side is a directed Segment.
618 See Also
619 ========
621 sympy.geometry.point.Point, sympy.geometry.line.Segment
623 Examples
624 ========
626 >>> from sympy import Point, Polygon
627 >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)])
628 >>> poly = Polygon(p1, p2, p3, p4)
629 >>> poly.sides
630 [Segment2D(Point2D(0, 0), Point2D(1, 0)),
631 Segment2D(Point2D(1, 0), Point2D(5, 1)),
632 Segment2D(Point2D(5, 1), Point2D(0, 1)), Segment2D(Point2D(0, 1), Point2D(0, 0))]
634 """
635 res = []
636 args = self.vertices
637 for i in range(-len(args), 0):
638 res.append(Segment(args[i], args[i + 1]))
639 return res
641 @property
642 def bounds(self):
643 """Return a tuple (xmin, ymin, xmax, ymax) representing the bounding
644 rectangle for the geometric figure.
646 """
648 verts = self.vertices
649 xs = [p.x for p in verts]
650 ys = [p.y for p in verts]
651 return (min(xs), min(ys), max(xs), max(ys))
653 def is_convex(self):
654 """Is the polygon convex?
656 A polygon is convex if all its interior angles are less than 180
657 degrees and there are no intersections between sides.
659 Returns
660 =======
662 is_convex : boolean
663 True if this polygon is convex, False otherwise.
665 See Also
666 ========
668 sympy.geometry.util.convex_hull
670 Examples
671 ========
673 >>> from sympy import Point, Polygon
674 >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)])
675 >>> poly = Polygon(p1, p2, p3, p4)
676 >>> poly.is_convex()
677 True
679 """
680 # Determine orientation of points
681 args = self.vertices
682 cw = self._isright(args[-2], args[-1], args[0])
683 for i in range(1, len(args)):
684 if cw ^ self._isright(args[i - 2], args[i - 1], args[i]):
685 return False
686 # check for intersecting sides
687 sides = self.sides
688 for i, si in enumerate(sides):
689 pts = si.args
690 # exclude the sides connected to si
691 for j in range(1 if i == len(sides) - 1 else 0, i - 1):
692 sj = sides[j]
693 if sj.p1 not in pts and sj.p2 not in pts:
694 hit = si.intersection(sj)
695 if hit:
696 return False
697 return True
699 def encloses_point(self, p):
700 """
701 Return True if p is enclosed by (is inside of) self.
703 Notes
704 =====
706 Being on the border of self is considered False.
708 Parameters
709 ==========
711 p : Point
713 Returns
714 =======
716 encloses_point : True, False or None
718 See Also
719 ========
721 sympy.geometry.point.Point, sympy.geometry.ellipse.Ellipse.encloses_point
723 Examples
724 ========
726 >>> from sympy import Polygon, Point
727 >>> p = Polygon((0, 0), (4, 0), (4, 4))
728 >>> p.encloses_point(Point(2, 1))
729 True
730 >>> p.encloses_point(Point(2, 2))
731 False
732 >>> p.encloses_point(Point(5, 5))
733 False
735 References
736 ==========
738 .. [1] http://paulbourke.net/geometry/polygonmesh/#insidepoly
740 """
741 p = Point(p, dim=2)
742 if p in self.vertices or any(p in s for s in self.sides):
743 return False
745 # move to p, checking that the result is numeric
746 lit = []
747 for v in self.vertices:
748 lit.append(v - p) # the difference is simplified
749 if lit[-1].free_symbols:
750 return None
752 poly = Polygon(*lit)
754 # polygon closure is assumed in the following test but Polygon removes duplicate pts so
755 # the last point has to be added so all sides are computed. Using Polygon.sides is
756 # not good since Segments are unordered.
757 args = poly.args
758 indices = list(range(-len(args), 1))
760 if poly.is_convex():
761 orientation = None
762 for i in indices:
763 a = args[i]
764 b = args[i + 1]
765 test = ((-a.y)*(b.x - a.x) - (-a.x)*(b.y - a.y)).is_negative
766 if orientation is None:
767 orientation = test
768 elif test is not orientation:
769 return False
770 return True
772 hit_odd = False
773 p1x, p1y = args[0].args
774 for i in indices[1:]:
775 p2x, p2y = args[i].args
776 if 0 > min(p1y, p2y):
777 if 0 <= max(p1y, p2y):
778 if 0 <= max(p1x, p2x):
779 if p1y != p2y:
780 xinters = (-p1y)*(p2x - p1x)/(p2y - p1y) + p1x
781 if p1x == p2x or 0 <= xinters:
782 hit_odd = not hit_odd
783 p1x, p1y = p2x, p2y
784 return hit_odd
786 def arbitrary_point(self, parameter='t'):
787 """A parameterized point on the polygon.
789 The parameter, varying from 0 to 1, assigns points to the position on
790 the perimeter that is that fraction of the total perimeter. So the
791 point evaluated at t=1/2 would return the point from the first vertex
792 that is 1/2 way around the polygon.
794 Parameters
795 ==========
797 parameter : str, optional
798 Default value is 't'.
800 Returns
801 =======
803 arbitrary_point : Point
805 Raises
806 ======
808 ValueError
809 When `parameter` already appears in the Polygon's definition.
811 See Also
812 ========
814 sympy.geometry.point.Point
816 Examples
817 ========
819 >>> from sympy import Polygon, Symbol
820 >>> t = Symbol('t', real=True)
821 >>> tri = Polygon((0, 0), (1, 0), (1, 1))
822 >>> p = tri.arbitrary_point('t')
823 >>> perimeter = tri.perimeter
824 >>> s1, s2 = [s.length for s in tri.sides[:2]]
825 >>> p.subs(t, (s1 + s2/2)/perimeter)
826 Point2D(1, 1/2)
828 """
829 t = _symbol(parameter, real=True)
830 if t.name in (f.name for f in self.free_symbols):
831 raise ValueError('Symbol %s already appears in object and cannot be used as a parameter.' % t.name)
832 sides = []
833 perimeter = self.perimeter
834 perim_fraction_start = 0
835 for s in self.sides:
836 side_perim_fraction = s.length/perimeter
837 perim_fraction_end = perim_fraction_start + side_perim_fraction
838 pt = s.arbitrary_point(parameter).subs(
839 t, (t - perim_fraction_start)/side_perim_fraction)
840 sides.append(
841 (pt, (And(perim_fraction_start <= t, t < perim_fraction_end))))
842 perim_fraction_start = perim_fraction_end
843 return Piecewise(*sides)
845 def parameter_value(self, other, t):
846 if not isinstance(other,GeometryEntity):
847 other = Point(other, dim=self.ambient_dimension)
848 if not isinstance(other,Point):
849 raise ValueError("other must be a point")
850 if other.free_symbols:
851 raise NotImplementedError('non-numeric coordinates')
852 unknown = False
853 p = self.arbitrary_point(T)
854 for pt, cond in p.args:
855 sol = solve(pt - other, T, dict=True)
856 if not sol:
857 continue
858 value = sol[0][T]
859 if simplify(cond.subs(T, value)) == True:
860 return {t: value}
861 unknown = True
862 if unknown:
863 raise ValueError("Given point may not be on %s" % func_name(self))
864 raise ValueError("Given point is not on %s" % func_name(self))
866 def plot_interval(self, parameter='t'):
867 """The plot interval for the default geometric plot of the polygon.
869 Parameters
870 ==========
872 parameter : str, optional
873 Default value is 't'.
875 Returns
876 =======
878 plot_interval : list (plot interval)
879 [parameter, lower_bound, upper_bound]
881 Examples
882 ========
884 >>> from sympy import Polygon
885 >>> p = Polygon((0, 0), (1, 0), (1, 1))
886 >>> p.plot_interval()
887 [t, 0, 1]
889 """
890 t = Symbol(parameter, real=True)
891 return [t, 0, 1]
893 def intersection(self, o):
894 """The intersection of polygon and geometry entity.
896 The intersection may be empty and can contain individual Points and
897 complete Line Segments.
899 Parameters
900 ==========
902 other: GeometryEntity
904 Returns
905 =======
907 intersection : list
908 The list of Segments and Points
910 See Also
911 ========
913 sympy.geometry.point.Point, sympy.geometry.line.Segment
915 Examples
916 ========
918 >>> from sympy import Point, Polygon, Line
919 >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)])
920 >>> poly1 = Polygon(p1, p2, p3, p4)
921 >>> p5, p6, p7 = map(Point, [(3, 2), (1, -1), (0, 2)])
922 >>> poly2 = Polygon(p5, p6, p7)
923 >>> poly1.intersection(poly2)
924 [Point2D(1/3, 1), Point2D(2/3, 0), Point2D(9/5, 1/5), Point2D(7/3, 1)]
925 >>> poly1.intersection(Line(p1, p2))
926 [Segment2D(Point2D(0, 0), Point2D(1, 0))]
927 >>> poly1.intersection(p1)
928 [Point2D(0, 0)]
929 """
930 intersection_result = []
931 k = o.sides if isinstance(o, Polygon) else [o]
932 for side in self.sides:
933 for side1 in k:
934 intersection_result.extend(side.intersection(side1))
936 intersection_result = list(uniq(intersection_result))
937 points = [entity for entity in intersection_result if isinstance(entity, Point)]
938 segments = [entity for entity in intersection_result if isinstance(entity, Segment)]
940 if points and segments:
941 points_in_segments = list(uniq([point for point in points for segment in segments if point in segment]))
942 if points_in_segments:
943 for i in points_in_segments:
944 points.remove(i)
945 return list(ordered(segments + points))
946 else:
947 return list(ordered(intersection_result))
950 def cut_section(self, line):
951 """
952 Returns a tuple of two polygon segments that lie above and below
953 the intersecting line respectively.
955 Parameters
956 ==========
958 line: Line object of geometry module
959 line which cuts the Polygon. The part of the Polygon that lies
960 above and below this line is returned.
962 Returns
963 =======
965 upper_polygon, lower_polygon: Polygon objects or None
966 upper_polygon is the polygon that lies above the given line.
967 lower_polygon is the polygon that lies below the given line.
968 upper_polygon and lower polygon are ``None`` when no polygon
969 exists above the line or below the line.
971 Raises
972 ======
974 ValueError: When the line does not intersect the polygon
976 Examples
977 ========
979 >>> from sympy import Polygon, Line
980 >>> a, b = 20, 10
981 >>> p1, p2, p3, p4 = [(0, b), (0, 0), (a, 0), (a, b)]
982 >>> rectangle = Polygon(p1, p2, p3, p4)
983 >>> t = rectangle.cut_section(Line((0, 5), slope=0))
984 >>> t
985 (Polygon(Point2D(0, 10), Point2D(0, 5), Point2D(20, 5), Point2D(20, 10)),
986 Polygon(Point2D(0, 5), Point2D(0, 0), Point2D(20, 0), Point2D(20, 5)))
987 >>> upper_segment, lower_segment = t
988 >>> upper_segment.area
989 100
990 >>> upper_segment.centroid
991 Point2D(10, 15/2)
992 >>> lower_segment.centroid
993 Point2D(10, 5/2)
995 References
996 ==========
998 .. [1] https://github.com/sympy/sympy/wiki/A-method-to-return-a-cut-section-of-any-polygon-geometry
1000 """
1001 intersection_points = self.intersection(line)
1002 if not intersection_points:
1003 raise ValueError("This line does not intersect the polygon")
1005 points = list(self.vertices)
1006 points.append(points[0])
1008 eq = line.equation(x, y)
1010 # considering equation of line to be `ax +by + c`
1011 a = eq.coeff(x)
1012 b = eq.coeff(y)
1014 upper_vertices = []
1015 lower_vertices = []
1016 # prev is true when previous point is above the line
1017 prev = True
1018 prev_point = None
1019 for point in points:
1020 # when coefficient of y is 0, right side of the line is
1021 # considered
1022 compare = eq.subs({x: point.x, y: point.y})/b if b \
1023 else eq.subs(x, point.x)/a
1025 # if point lies above line
1026 if compare > 0:
1027 if not prev:
1028 # if previous point lies below the line, the intersection
1029 # point of the polygon edge and the line has to be included
1030 edge = Line(point, prev_point)
1031 new_point = edge.intersection(line)
1032 upper_vertices.append(new_point[0])
1033 lower_vertices.append(new_point[0])
1035 upper_vertices.append(point)
1036 prev = True
1037 else:
1038 if prev and prev_point:
1039 edge = Line(point, prev_point)
1040 new_point = edge.intersection(line)
1041 upper_vertices.append(new_point[0])
1042 lower_vertices.append(new_point[0])
1043 lower_vertices.append(point)
1044 prev = False
1045 prev_point = point
1047 upper_polygon, lower_polygon = None, None
1048 if upper_vertices and isinstance(Polygon(*upper_vertices), Polygon):
1049 upper_polygon = Polygon(*upper_vertices)
1050 if lower_vertices and isinstance(Polygon(*lower_vertices), Polygon):
1051 lower_polygon = Polygon(*lower_vertices)
1053 return upper_polygon, lower_polygon
1056 def distance(self, o):
1057 """
1058 Returns the shortest distance between self and o.
1060 If o is a point, then self does not need to be convex.
1061 If o is another polygon self and o must be convex.
1063 Examples
1064 ========
1066 >>> from sympy import Point, Polygon, RegularPolygon
1067 >>> p1, p2 = map(Point, [(0, 0), (7, 5)])
1068 >>> poly = Polygon(*RegularPolygon(p1, 1, 3).vertices)
1069 >>> poly.distance(p2)
1070 sqrt(61)
1071 """
1072 if isinstance(o, Point):
1073 dist = oo
1074 for side in self.sides:
1075 current = side.distance(o)
1076 if current == 0:
1077 return S.Zero
1078 elif current < dist:
1079 dist = current
1080 return dist
1081 elif isinstance(o, Polygon) and self.is_convex() and o.is_convex():
1082 return self._do_poly_distance(o)
1083 raise NotImplementedError()
1085 def _do_poly_distance(self, e2):
1086 """
1087 Calculates the least distance between the exteriors of two
1088 convex polygons e1 and e2. Does not check for the convexity
1089 of the polygons as this is checked by Polygon.distance.
1091 Notes
1092 =====
1094 - Prints a warning if the two polygons possibly intersect as the return
1095 value will not be valid in such a case. For a more through test of
1096 intersection use intersection().
1098 See Also
1099 ========
1101 sympy.geometry.point.Point.distance
1103 Examples
1104 ========
1106 >>> from sympy import Point, Polygon
1107 >>> square = Polygon(Point(0, 0), Point(0, 1), Point(1, 1), Point(1, 0))
1108 >>> triangle = Polygon(Point(1, 2), Point(2, 2), Point(2, 1))
1109 >>> square._do_poly_distance(triangle)
1110 sqrt(2)/2
1112 Description of method used
1113 ==========================
1115 Method:
1116 [1] https://web.archive.org/web/20150509035744/http://cgm.cs.mcgill.ca/~orm/mind2p.html
1117 Uses rotating calipers:
1118 [2] https://en.wikipedia.org/wiki/Rotating_calipers
1119 and antipodal points:
1120 [3] https://en.wikipedia.org/wiki/Antipodal_point
1121 """
1122 e1 = self
1124 '''Tests for a possible intersection between the polygons and outputs a warning'''
1125 e1_center = e1.centroid
1126 e2_center = e2.centroid
1127 e1_max_radius = S.Zero
1128 e2_max_radius = S.Zero
1129 for vertex in e1.vertices:
1130 r = Point.distance(e1_center, vertex)
1131 if e1_max_radius < r:
1132 e1_max_radius = r
1133 for vertex in e2.vertices:
1134 r = Point.distance(e2_center, vertex)
1135 if e2_max_radius < r:
1136 e2_max_radius = r
1137 center_dist = Point.distance(e1_center, e2_center)
1138 if center_dist <= e1_max_radius + e2_max_radius:
1139 warnings.warn("Polygons may intersect producing erroneous output",
1140 stacklevel=3)
1142 '''
1143 Find the upper rightmost vertex of e1 and the lowest leftmost vertex of e2
1144 '''
1145 e1_ymax = Point(0, -oo)
1146 e2_ymin = Point(0, oo)
1148 for vertex in e1.vertices:
1149 if vertex.y > e1_ymax.y or (vertex.y == e1_ymax.y and vertex.x > e1_ymax.x):
1150 e1_ymax = vertex
1151 for vertex in e2.vertices:
1152 if vertex.y < e2_ymin.y or (vertex.y == e2_ymin.y and vertex.x < e2_ymin.x):
1153 e2_ymin = vertex
1154 min_dist = Point.distance(e1_ymax, e2_ymin)
1156 '''
1157 Produce a dictionary with vertices of e1 as the keys and, for each vertex, the points
1158 to which the vertex is connected as its value. The same is then done for e2.
1159 '''
1160 e1_connections = {}
1161 e2_connections = {}
1163 for side in e1.sides:
1164 if side.p1 in e1_connections:
1165 e1_connections[side.p1].append(side.p2)
1166 else:
1167 e1_connections[side.p1] = [side.p2]
1169 if side.p2 in e1_connections:
1170 e1_connections[side.p2].append(side.p1)
1171 else:
1172 e1_connections[side.p2] = [side.p1]
1174 for side in e2.sides:
1175 if side.p1 in e2_connections:
1176 e2_connections[side.p1].append(side.p2)
1177 else:
1178 e2_connections[side.p1] = [side.p2]
1180 if side.p2 in e2_connections:
1181 e2_connections[side.p2].append(side.p1)
1182 else:
1183 e2_connections[side.p2] = [side.p1]
1185 e1_current = e1_ymax
1186 e2_current = e2_ymin
1187 support_line = Line(Point(S.Zero, S.Zero), Point(S.One, S.Zero))
1189 '''
1190 Determine which point in e1 and e2 will be selected after e2_ymin and e1_ymax,
1191 this information combined with the above produced dictionaries determines the
1192 path that will be taken around the polygons
1193 '''
1194 point1 = e1_connections[e1_ymax][0]
1195 point2 = e1_connections[e1_ymax][1]
1196 angle1 = support_line.angle_between(Line(e1_ymax, point1))
1197 angle2 = support_line.angle_between(Line(e1_ymax, point2))
1198 if angle1 < angle2:
1199 e1_next = point1
1200 elif angle2 < angle1:
1201 e1_next = point2
1202 elif Point.distance(e1_ymax, point1) > Point.distance(e1_ymax, point2):
1203 e1_next = point2
1204 else:
1205 e1_next = point1
1207 point1 = e2_connections[e2_ymin][0]
1208 point2 = e2_connections[e2_ymin][1]
1209 angle1 = support_line.angle_between(Line(e2_ymin, point1))
1210 angle2 = support_line.angle_between(Line(e2_ymin, point2))
1211 if angle1 > angle2:
1212 e2_next = point1
1213 elif angle2 > angle1:
1214 e2_next = point2
1215 elif Point.distance(e2_ymin, point1) > Point.distance(e2_ymin, point2):
1216 e2_next = point2
1217 else:
1218 e2_next = point1
1220 '''
1221 Loop which determines the distance between anti-podal pairs and updates the
1222 minimum distance accordingly. It repeats until it reaches the starting position.
1223 '''
1224 while True:
1225 e1_angle = support_line.angle_between(Line(e1_current, e1_next))
1226 e2_angle = pi - support_line.angle_between(Line(
1227 e2_current, e2_next))
1229 if (e1_angle < e2_angle) is True:
1230 support_line = Line(e1_current, e1_next)
1231 e1_segment = Segment(e1_current, e1_next)
1232 min_dist_current = e1_segment.distance(e2_current)
1234 if min_dist_current.evalf() < min_dist.evalf():
1235 min_dist = min_dist_current
1237 if e1_connections[e1_next][0] != e1_current:
1238 e1_current = e1_next
1239 e1_next = e1_connections[e1_next][0]
1240 else:
1241 e1_current = e1_next
1242 e1_next = e1_connections[e1_next][1]
1243 elif (e1_angle > e2_angle) is True:
1244 support_line = Line(e2_next, e2_current)
1245 e2_segment = Segment(e2_current, e2_next)
1246 min_dist_current = e2_segment.distance(e1_current)
1248 if min_dist_current.evalf() < min_dist.evalf():
1249 min_dist = min_dist_current
1251 if e2_connections[e2_next][0] != e2_current:
1252 e2_current = e2_next
1253 e2_next = e2_connections[e2_next][0]
1254 else:
1255 e2_current = e2_next
1256 e2_next = e2_connections[e2_next][1]
1257 else:
1258 support_line = Line(e1_current, e1_next)
1259 e1_segment = Segment(e1_current, e1_next)
1260 e2_segment = Segment(e2_current, e2_next)
1261 min1 = e1_segment.distance(e2_next)
1262 min2 = e2_segment.distance(e1_next)
1264 min_dist_current = min(min1, min2)
1265 if min_dist_current.evalf() < min_dist.evalf():
1266 min_dist = min_dist_current
1268 if e1_connections[e1_next][0] != e1_current:
1269 e1_current = e1_next
1270 e1_next = e1_connections[e1_next][0]
1271 else:
1272 e1_current = e1_next
1273 e1_next = e1_connections[e1_next][1]
1275 if e2_connections[e2_next][0] != e2_current:
1276 e2_current = e2_next
1277 e2_next = e2_connections[e2_next][0]
1278 else:
1279 e2_current = e2_next
1280 e2_next = e2_connections[e2_next][1]
1281 if e1_current == e1_ymax and e2_current == e2_ymin:
1282 break
1283 return min_dist
1285 def _svg(self, scale_factor=1., fill_color="#66cc99"):
1286 """Returns SVG path element for the Polygon.
1288 Parameters
1289 ==========
1291 scale_factor : float
1292 Multiplication factor for the SVG stroke-width. Default is 1.
1293 fill_color : str, optional
1294 Hex string for fill color. Default is "#66cc99".
1295 """
1296 verts = map(N, self.vertices)
1297 coords = ["{},{}".format(p.x, p.y) for p in verts]
1298 path = "M {} L {} z".format(coords[0], " L ".join(coords[1:]))
1299 return (
1300 '<path fill-rule="evenodd" fill="{2}" stroke="#555555" '
1301 'stroke-width="{0}" opacity="0.6" d="{1}" />'
1302 ).format(2. * scale_factor, path, fill_color)
1304 def _hashable_content(self):
1306 D = {}
1307 def ref_list(point_list):
1308 kee = {}
1309 for i, p in enumerate(ordered(set(point_list))):
1310 kee[p] = i
1311 D[i] = p
1312 return [kee[p] for p in point_list]
1314 S1 = ref_list(self.args)
1315 r_nor = rotate_left(S1, least_rotation(S1))
1316 S2 = ref_list(list(reversed(self.args)))
1317 r_rev = rotate_left(S2, least_rotation(S2))
1318 if r_nor < r_rev:
1319 r = r_nor
1320 else:
1321 r = r_rev
1322 canonical_args = [ D[order] for order in r ]
1323 return tuple(canonical_args)
1325 def __contains__(self, o):
1326 """
1327 Return True if o is contained within the boundary lines of self.altitudes
1329 Parameters
1330 ==========
1332 other : GeometryEntity
1334 Returns
1335 =======
1337 contained in : bool
1338 The points (and sides, if applicable) are contained in self.
1340 See Also
1341 ========
1343 sympy.geometry.entity.GeometryEntity.encloses
1345 Examples
1346 ========
1348 >>> from sympy import Line, Segment, Point
1349 >>> p = Point(0, 0)
1350 >>> q = Point(1, 1)
1351 >>> s = Segment(p, q*2)
1352 >>> l = Line(p, q)
1353 >>> p in q
1354 False
1355 >>> p in s
1356 True
1357 >>> q*3 in s
1358 False
1359 >>> s in l
1360 True
1362 """
1364 if isinstance(o, Polygon):
1365 return self == o
1366 elif isinstance(o, Segment):
1367 return any(o in s for s in self.sides)
1368 elif isinstance(o, Point):
1369 if o in self.vertices:
1370 return True
1371 for side in self.sides:
1372 if o in side:
1373 return True
1375 return False
1377 def bisectors(p, prec=None):
1378 """Returns angle bisectors of a polygon. If prec is given
1379 then approximate the point defining the ray to that precision.
1381 The distance between the points defining the bisector ray is 1.
1383 Examples
1384 ========
1386 >>> from sympy import Polygon, Point
1387 >>> p = Polygon(Point(0, 0), Point(2, 0), Point(1, 1), Point(0, 3))
1388 >>> p.bisectors(2)
1389 {Point2D(0, 0): Ray2D(Point2D(0, 0), Point2D(0.71, 0.71)),
1390 Point2D(0, 3): Ray2D(Point2D(0, 3), Point2D(0.23, 2.0)),
1391 Point2D(1, 1): Ray2D(Point2D(1, 1), Point2D(0.19, 0.42)),
1392 Point2D(2, 0): Ray2D(Point2D(2, 0), Point2D(1.1, 0.38))}
1393 """
1394 b = {}
1395 pts = list(p.args)
1396 pts.append(pts[0]) # close it
1397 cw = Polygon._isright(*pts[:3])
1398 if cw:
1399 pts = list(reversed(pts))
1400 for v, a in p.angles.items():
1401 i = pts.index(v)
1402 p1, p2 = Point._normalize_dimension(pts[i], pts[i + 1])
1403 ray = Ray(p1, p2).rotate(a/2, v)
1404 dir = ray.direction
1405 ray = Ray(ray.p1, ray.p1 + dir/dir.distance((0, 0)))
1406 if prec is not None:
1407 ray = Ray(ray.p1, ray.p2.n(prec))
1408 b[v] = ray
1409 return b
1412class RegularPolygon(Polygon):
1413 """
1414 A regular polygon.
1416 Such a polygon has all internal angles equal and all sides the same length.
1418 Parameters
1419 ==========
1421 center : Point
1422 radius : number or Basic instance
1423 The distance from the center to a vertex
1424 n : int
1425 The number of sides
1427 Attributes
1428 ==========
1430 vertices
1431 center
1432 radius
1433 rotation
1434 apothem
1435 interior_angle
1436 exterior_angle
1437 circumcircle
1438 incircle
1439 angles
1441 Raises
1442 ======
1444 GeometryError
1445 If the `center` is not a Point, or the `radius` is not a number or Basic
1446 instance, or the number of sides, `n`, is less than three.
1448 Notes
1449 =====
1451 A RegularPolygon can be instantiated with Polygon with the kwarg n.
1453 Regular polygons are instantiated with a center, radius, number of sides
1454 and a rotation angle. Whereas the arguments of a Polygon are vertices, the
1455 vertices of the RegularPolygon must be obtained with the vertices method.
1457 See Also
1458 ========
1460 sympy.geometry.point.Point, Polygon
1462 Examples
1463 ========
1465 >>> from sympy import RegularPolygon, Point
1466 >>> r = RegularPolygon(Point(0, 0), 5, 3)
1467 >>> r
1468 RegularPolygon(Point2D(0, 0), 5, 3, 0)
1469 >>> r.vertices[0]
1470 Point2D(5, 0)
1472 """
1474 __slots__ = ('_n', '_center', '_radius', '_rot')
1476 def __new__(self, c, r, n, rot=0, **kwargs):
1477 r, n, rot = map(sympify, (r, n, rot))
1478 c = Point(c, dim=2, **kwargs)
1479 if not isinstance(r, Expr):
1480 raise GeometryError("r must be an Expr object, not %s" % r)
1481 if n.is_Number:
1482 as_int(n) # let an error raise if necessary
1483 if n < 3:
1484 raise GeometryError("n must be a >= 3, not %s" % n)
1486 obj = GeometryEntity.__new__(self, c, r, n, **kwargs)
1487 obj._n = n
1488 obj._center = c
1489 obj._radius = r
1490 obj._rot = rot % (2*S.Pi/n) if rot.is_number else rot
1491 return obj
1493 def _eval_evalf(self, prec=15, **options):
1494 c, r, n, a = self.args
1495 dps = prec_to_dps(prec)
1496 c, r, a = [i.evalf(n=dps, **options) for i in (c, r, a)]
1497 return self.func(c, r, n, a)
1499 @property
1500 def args(self):
1501 """
1502 Returns the center point, the radius,
1503 the number of sides, and the orientation angle.
1505 Examples
1506 ========
1508 >>> from sympy import RegularPolygon, Point
1509 >>> r = RegularPolygon(Point(0, 0), 5, 3)
1510 >>> r.args
1511 (Point2D(0, 0), 5, 3, 0)
1512 """
1513 return self._center, self._radius, self._n, self._rot
1515 def __str__(self):
1516 return 'RegularPolygon(%s, %s, %s, %s)' % tuple(self.args)
1518 def __repr__(self):
1519 return 'RegularPolygon(%s, %s, %s, %s)' % tuple(self.args)
1521 @property
1522 def area(self):
1523 """Returns the area.
1525 Examples
1526 ========
1528 >>> from sympy import RegularPolygon
1529 >>> square = RegularPolygon((0, 0), 1, 4)
1530 >>> square.area
1531 2
1532 >>> _ == square.length**2
1533 True
1534 """
1535 c, r, n, rot = self.args
1536 return sign(r)*n*self.length**2/(4*tan(pi/n))
1538 @property
1539 def length(self):
1540 """Returns the length of the sides.
1542 The half-length of the side and the apothem form two legs
1543 of a right triangle whose hypotenuse is the radius of the
1544 regular polygon.
1546 Examples
1547 ========
1549 >>> from sympy import RegularPolygon
1550 >>> from sympy import sqrt
1551 >>> s = square_in_unit_circle = RegularPolygon((0, 0), 1, 4)
1552 >>> s.length
1553 sqrt(2)
1554 >>> sqrt((_/2)**2 + s.apothem**2) == s.radius
1555 True
1557 """
1558 return self.radius*2*sin(pi/self._n)
1560 @property
1561 def center(self):
1562 """The center of the RegularPolygon
1564 This is also the center of the circumscribing circle.
1566 Returns
1567 =======
1569 center : Point
1571 See Also
1572 ========
1574 sympy.geometry.point.Point, sympy.geometry.ellipse.Ellipse.center
1576 Examples
1577 ========
1579 >>> from sympy import RegularPolygon, Point
1580 >>> rp = RegularPolygon(Point(0, 0), 5, 4)
1581 >>> rp.center
1582 Point2D(0, 0)
1583 """
1584 return self._center
1586 centroid = center
1588 @property
1589 def circumcenter(self):
1590 """
1591 Alias for center.
1593 Examples
1594 ========
1596 >>> from sympy import RegularPolygon, Point
1597 >>> rp = RegularPolygon(Point(0, 0), 5, 4)
1598 >>> rp.circumcenter
1599 Point2D(0, 0)
1600 """
1601 return self.center
1603 @property
1604 def radius(self):
1605 """Radius of the RegularPolygon
1607 This is also the radius of the circumscribing circle.
1609 Returns
1610 =======
1612 radius : number or instance of Basic
1614 See Also
1615 ========
1617 sympy.geometry.line.Segment.length, sympy.geometry.ellipse.Circle.radius
1619 Examples
1620 ========
1622 >>> from sympy import Symbol
1623 >>> from sympy import RegularPolygon, Point
1624 >>> radius = Symbol('r')
1625 >>> rp = RegularPolygon(Point(0, 0), radius, 4)
1626 >>> rp.radius
1627 r
1629 """
1630 return self._radius
1632 @property
1633 def circumradius(self):
1634 """
1635 Alias for radius.
1637 Examples
1638 ========
1640 >>> from sympy import Symbol
1641 >>> from sympy import RegularPolygon, Point
1642 >>> radius = Symbol('r')
1643 >>> rp = RegularPolygon(Point(0, 0), radius, 4)
1644 >>> rp.circumradius
1645 r
1646 """
1647 return self.radius
1649 @property
1650 def rotation(self):
1651 """CCW angle by which the RegularPolygon is rotated
1653 Returns
1654 =======
1656 rotation : number or instance of Basic
1658 Examples
1659 ========
1661 >>> from sympy import pi
1662 >>> from sympy.abc import a
1663 >>> from sympy import RegularPolygon, Point
1664 >>> RegularPolygon(Point(0, 0), 3, 4, pi/4).rotation
1665 pi/4
1667 Numerical rotation angles are made canonical:
1669 >>> RegularPolygon(Point(0, 0), 3, 4, a).rotation
1670 a
1671 >>> RegularPolygon(Point(0, 0), 3, 4, pi).rotation
1672 0
1674 """
1675 return self._rot
1677 @property
1678 def apothem(self):
1679 """The inradius of the RegularPolygon.
1681 The apothem/inradius is the radius of the inscribed circle.
1683 Returns
1684 =======
1686 apothem : number or instance of Basic
1688 See Also
1689 ========
1691 sympy.geometry.line.Segment.length, sympy.geometry.ellipse.Circle.radius
1693 Examples
1694 ========
1696 >>> from sympy import Symbol
1697 >>> from sympy import RegularPolygon, Point
1698 >>> radius = Symbol('r')
1699 >>> rp = RegularPolygon(Point(0, 0), radius, 4)
1700 >>> rp.apothem
1701 sqrt(2)*r/2
1703 """
1704 return self.radius * cos(S.Pi/self._n)
1706 @property
1707 def inradius(self):
1708 """
1709 Alias for apothem.
1711 Examples
1712 ========
1714 >>> from sympy import Symbol
1715 >>> from sympy import RegularPolygon, Point
1716 >>> radius = Symbol('r')
1717 >>> rp = RegularPolygon(Point(0, 0), radius, 4)
1718 >>> rp.inradius
1719 sqrt(2)*r/2
1720 """
1721 return self.apothem
1723 @property
1724 def interior_angle(self):
1725 """Measure of the interior angles.
1727 Returns
1728 =======
1730 interior_angle : number
1732 See Also
1733 ========
1735 sympy.geometry.line.LinearEntity.angle_between
1737 Examples
1738 ========
1740 >>> from sympy import RegularPolygon, Point
1741 >>> rp = RegularPolygon(Point(0, 0), 4, 8)
1742 >>> rp.interior_angle
1743 3*pi/4
1745 """
1746 return (self._n - 2)*S.Pi/self._n
1748 @property
1749 def exterior_angle(self):
1750 """Measure of the exterior angles.
1752 Returns
1753 =======
1755 exterior_angle : number
1757 See Also
1758 ========
1760 sympy.geometry.line.LinearEntity.angle_between
1762 Examples
1763 ========
1765 >>> from sympy import RegularPolygon, Point
1766 >>> rp = RegularPolygon(Point(0, 0), 4, 8)
1767 >>> rp.exterior_angle
1768 pi/4
1770 """
1771 return 2*S.Pi/self._n
1773 @property
1774 def circumcircle(self):
1775 """The circumcircle of the RegularPolygon.
1777 Returns
1778 =======
1780 circumcircle : Circle
1782 See Also
1783 ========
1785 circumcenter, sympy.geometry.ellipse.Circle
1787 Examples
1788 ========
1790 >>> from sympy import RegularPolygon, Point
1791 >>> rp = RegularPolygon(Point(0, 0), 4, 8)
1792 >>> rp.circumcircle
1793 Circle(Point2D(0, 0), 4)
1795 """
1796 return Circle(self.center, self.radius)
1798 @property
1799 def incircle(self):
1800 """The incircle of the RegularPolygon.
1802 Returns
1803 =======
1805 incircle : Circle
1807 See Also
1808 ========
1810 inradius, sympy.geometry.ellipse.Circle
1812 Examples
1813 ========
1815 >>> from sympy import RegularPolygon, Point
1816 >>> rp = RegularPolygon(Point(0, 0), 4, 7)
1817 >>> rp.incircle
1818 Circle(Point2D(0, 0), 4*cos(pi/7))
1820 """
1821 return Circle(self.center, self.apothem)
1823 @property
1824 def angles(self):
1825 """
1826 Returns a dictionary with keys, the vertices of the Polygon,
1827 and values, the interior angle at each vertex.
1829 Examples
1830 ========
1832 >>> from sympy import RegularPolygon, Point
1833 >>> r = RegularPolygon(Point(0, 0), 5, 3)
1834 >>> r.angles
1835 {Point2D(-5/2, -5*sqrt(3)/2): pi/3,
1836 Point2D(-5/2, 5*sqrt(3)/2): pi/3,
1837 Point2D(5, 0): pi/3}
1838 """
1839 ret = {}
1840 ang = self.interior_angle
1841 for v in self.vertices:
1842 ret[v] = ang
1843 return ret
1845 def encloses_point(self, p):
1846 """
1847 Return True if p is enclosed by (is inside of) self.
1849 Notes
1850 =====
1852 Being on the border of self is considered False.
1854 The general Polygon.encloses_point method is called only if
1855 a point is not within or beyond the incircle or circumcircle,
1856 respectively.
1858 Parameters
1859 ==========
1861 p : Point
1863 Returns
1864 =======
1866 encloses_point : True, False or None
1868 See Also
1869 ========
1871 sympy.geometry.ellipse.Ellipse.encloses_point
1873 Examples
1874 ========
1876 >>> from sympy import RegularPolygon, S, Point, Symbol
1877 >>> p = RegularPolygon((0, 0), 3, 4)
1878 >>> p.encloses_point(Point(0, 0))
1879 True
1880 >>> r, R = p.inradius, p.circumradius
1881 >>> p.encloses_point(Point((r + R)/2, 0))
1882 True
1883 >>> p.encloses_point(Point(R/2, R/2 + (R - r)/10))
1884 False
1885 >>> t = Symbol('t', real=True)
1886 >>> p.encloses_point(p.arbitrary_point().subs(t, S.Half))
1887 False
1888 >>> p.encloses_point(Point(5, 5))
1889 False
1891 """
1893 c = self.center
1894 d = Segment(c, p).length
1895 if d >= self.radius:
1896 return False
1897 elif d < self.inradius:
1898 return True
1899 else:
1900 # now enumerate the RegularPolygon like a general polygon.
1901 return Polygon.encloses_point(self, p)
1903 def spin(self, angle):
1904 """Increment *in place* the virtual Polygon's rotation by ccw angle.
1906 See also: rotate method which moves the center.
1908 >>> from sympy import Polygon, Point, pi
1909 >>> r = Polygon(Point(0,0), 1, n=3)
1910 >>> r.vertices[0]
1911 Point2D(1, 0)
1912 >>> r.spin(pi/6)
1913 >>> r.vertices[0]
1914 Point2D(sqrt(3)/2, 1/2)
1916 See Also
1917 ========
1919 rotation
1920 rotate : Creates a copy of the RegularPolygon rotated about a Point
1922 """
1923 self._rot += angle
1925 def rotate(self, angle, pt=None):
1926 """Override GeometryEntity.rotate to first rotate the RegularPolygon
1927 about its center.
1929 >>> from sympy import Point, RegularPolygon, pi
1930 >>> t = RegularPolygon(Point(1, 0), 1, 3)
1931 >>> t.vertices[0] # vertex on x-axis
1932 Point2D(2, 0)
1933 >>> t.rotate(pi/2).vertices[0] # vertex on y axis now
1934 Point2D(0, 2)
1936 See Also
1937 ========
1939 rotation
1940 spin : Rotates a RegularPolygon in place
1942 """
1944 r = type(self)(*self.args) # need a copy or else changes are in-place
1945 r._rot += angle
1946 return GeometryEntity.rotate(r, angle, pt)
1948 def scale(self, x=1, y=1, pt=None):
1949 """Override GeometryEntity.scale since it is the radius that must be
1950 scaled (if x == y) or else a new Polygon must be returned.
1952 >>> from sympy import RegularPolygon
1954 Symmetric scaling returns a RegularPolygon:
1956 >>> RegularPolygon((0, 0), 1, 4).scale(2, 2)
1957 RegularPolygon(Point2D(0, 0), 2, 4, 0)
1959 Asymmetric scaling returns a kite as a Polygon:
1961 >>> RegularPolygon((0, 0), 1, 4).scale(2, 1)
1962 Polygon(Point2D(2, 0), Point2D(0, 1), Point2D(-2, 0), Point2D(0, -1))
1964 """
1965 if pt:
1966 pt = Point(pt, dim=2)
1967 return self.translate(*(-pt).args).scale(x, y).translate(*pt.args)
1968 if x != y:
1969 return Polygon(*self.vertices).scale(x, y)
1970 c, r, n, rot = self.args
1971 r *= x
1972 return self.func(c, r, n, rot)
1974 def reflect(self, line):
1975 """Override GeometryEntity.reflect since this is not made of only
1976 points.
1978 Examples
1979 ========
1981 >>> from sympy import RegularPolygon, Line
1983 >>> RegularPolygon((0, 0), 1, 4).reflect(Line((0, 1), slope=-2))
1984 RegularPolygon(Point2D(4/5, 2/5), -1, 4, atan(4/3))
1986 """
1987 c, r, n, rot = self.args
1988 v = self.vertices[0]
1989 d = v - c
1990 cc = c.reflect(line)
1991 vv = v.reflect(line)
1992 dd = vv - cc
1993 # calculate rotation about the new center
1994 # which will align the vertices
1995 l1 = Ray((0, 0), dd)
1996 l2 = Ray((0, 0), d)
1997 ang = l1.closing_angle(l2)
1998 rot += ang
1999 # change sign of radius as point traversal is reversed
2000 return self.func(cc, -r, n, rot)
2002 @property
2003 def vertices(self):
2004 """The vertices of the RegularPolygon.
2006 Returns
2007 =======
2009 vertices : list
2010 Each vertex is a Point.
2012 See Also
2013 ========
2015 sympy.geometry.point.Point
2017 Examples
2018 ========
2020 >>> from sympy import RegularPolygon, Point
2021 >>> rp = RegularPolygon(Point(0, 0), 5, 4)
2022 >>> rp.vertices
2023 [Point2D(5, 0), Point2D(0, 5), Point2D(-5, 0), Point2D(0, -5)]
2025 """
2026 c = self._center
2027 r = abs(self._radius)
2028 rot = self._rot
2029 v = 2*S.Pi/self._n
2031 return [Point(c.x + r*cos(k*v + rot), c.y + r*sin(k*v + rot))
2032 for k in range(self._n)]
2034 def __eq__(self, o):
2035 if not isinstance(o, Polygon):
2036 return False
2037 elif not isinstance(o, RegularPolygon):
2038 return Polygon.__eq__(o, self)
2039 return self.args == o.args
2041 def __hash__(self):
2042 return super().__hash__()
2045class Triangle(Polygon):
2046 """
2047 A polygon with three vertices and three sides.
2049 Parameters
2050 ==========
2052 points : sequence of Points
2053 keyword: asa, sas, or sss to specify sides/angles of the triangle
2055 Attributes
2056 ==========
2058 vertices
2059 altitudes
2060 orthocenter
2061 circumcenter
2062 circumradius
2063 circumcircle
2064 inradius
2065 incircle
2066 exradii
2067 medians
2068 medial
2069 nine_point_circle
2071 Raises
2072 ======
2074 GeometryError
2075 If the number of vertices is not equal to three, or one of the vertices
2076 is not a Point, or a valid keyword is not given.
2078 See Also
2079 ========
2081 sympy.geometry.point.Point, Polygon
2083 Examples
2084 ========
2086 >>> from sympy import Triangle, Point
2087 >>> Triangle(Point(0, 0), Point(4, 0), Point(4, 3))
2088 Triangle(Point2D(0, 0), Point2D(4, 0), Point2D(4, 3))
2090 Keywords sss, sas, or asa can be used to give the desired
2091 side lengths (in order) and interior angles (in degrees) that
2092 define the triangle:
2094 >>> Triangle(sss=(3, 4, 5))
2095 Triangle(Point2D(0, 0), Point2D(3, 0), Point2D(3, 4))
2096 >>> Triangle(asa=(30, 1, 30))
2097 Triangle(Point2D(0, 0), Point2D(1, 0), Point2D(1/2, sqrt(3)/6))
2098 >>> Triangle(sas=(1, 45, 2))
2099 Triangle(Point2D(0, 0), Point2D(2, 0), Point2D(sqrt(2)/2, sqrt(2)/2))
2101 """
2103 def __new__(cls, *args, **kwargs):
2104 if len(args) != 3:
2105 if 'sss' in kwargs:
2106 return _sss(*[simplify(a) for a in kwargs['sss']])
2107 if 'asa' in kwargs:
2108 return _asa(*[simplify(a) for a in kwargs['asa']])
2109 if 'sas' in kwargs:
2110 return _sas(*[simplify(a) for a in kwargs['sas']])
2111 msg = "Triangle instantiates with three points or a valid keyword."
2112 raise GeometryError(msg)
2114 vertices = [Point(a, dim=2, **kwargs) for a in args]
2116 # remove consecutive duplicates
2117 nodup = []
2118 for p in vertices:
2119 if nodup and p == nodup[-1]:
2120 continue
2121 nodup.append(p)
2122 if len(nodup) > 1 and nodup[-1] == nodup[0]:
2123 nodup.pop() # last point was same as first
2125 # remove collinear points
2126 i = -3
2127 while i < len(nodup) - 3 and len(nodup) > 2:
2128 a, b, c = sorted(
2129 [nodup[i], nodup[i + 1], nodup[i + 2]], key=default_sort_key)
2130 if Point.is_collinear(a, b, c):
2131 nodup[i] = a
2132 nodup[i + 1] = None
2133 nodup.pop(i + 1)
2134 i += 1
2136 vertices = list(filter(lambda x: x is not None, nodup))
2138 if len(vertices) == 3:
2139 return GeometryEntity.__new__(cls, *vertices, **kwargs)
2140 elif len(vertices) == 2:
2141 return Segment(*vertices, **kwargs)
2142 else:
2143 return Point(*vertices, **kwargs)
2145 @property
2146 def vertices(self):
2147 """The triangle's vertices
2149 Returns
2150 =======
2152 vertices : tuple
2153 Each element in the tuple is a Point
2155 See Also
2156 ========
2158 sympy.geometry.point.Point
2160 Examples
2161 ========
2163 >>> from sympy import Triangle, Point
2164 >>> t = Triangle(Point(0, 0), Point(4, 0), Point(4, 3))
2165 >>> t.vertices
2166 (Point2D(0, 0), Point2D(4, 0), Point2D(4, 3))
2168 """
2169 return self.args
2171 def is_similar(t1, t2):
2172 """Is another triangle similar to this one.
2174 Two triangles are similar if one can be uniformly scaled to the other.
2176 Parameters
2177 ==========
2179 other: Triangle
2181 Returns
2182 =======
2184 is_similar : boolean
2186 See Also
2187 ========
2189 sympy.geometry.entity.GeometryEntity.is_similar
2191 Examples
2192 ========
2194 >>> from sympy import Triangle, Point
2195 >>> t1 = Triangle(Point(0, 0), Point(4, 0), Point(4, 3))
2196 >>> t2 = Triangle(Point(0, 0), Point(-4, 0), Point(-4, -3))
2197 >>> t1.is_similar(t2)
2198 True
2200 >>> t2 = Triangle(Point(0, 0), Point(-4, 0), Point(-4, -4))
2201 >>> t1.is_similar(t2)
2202 False
2204 """
2205 if not isinstance(t2, Polygon):
2206 return False
2208 s1_1, s1_2, s1_3 = [side.length for side in t1.sides]
2209 s2 = [side.length for side in t2.sides]
2211 def _are_similar(u1, u2, u3, v1, v2, v3):
2212 e1 = simplify(u1/v1)
2213 e2 = simplify(u2/v2)
2214 e3 = simplify(u3/v3)
2215 return bool(e1 == e2) and bool(e2 == e3)
2217 # There's only 6 permutations, so write them out
2218 return _are_similar(s1_1, s1_2, s1_3, *s2) or \
2219 _are_similar(s1_1, s1_3, s1_2, *s2) or \
2220 _are_similar(s1_2, s1_1, s1_3, *s2) or \
2221 _are_similar(s1_2, s1_3, s1_1, *s2) or \
2222 _are_similar(s1_3, s1_1, s1_2, *s2) or \
2223 _are_similar(s1_3, s1_2, s1_1, *s2)
2225 def is_equilateral(self):
2226 """Are all the sides the same length?
2228 Returns
2229 =======
2231 is_equilateral : boolean
2233 See Also
2234 ========
2236 sympy.geometry.entity.GeometryEntity.is_similar, RegularPolygon
2237 is_isosceles, is_right, is_scalene
2239 Examples
2240 ========
2242 >>> from sympy import Triangle, Point
2243 >>> t1 = Triangle(Point(0, 0), Point(4, 0), Point(4, 3))
2244 >>> t1.is_equilateral()
2245 False
2247 >>> from sympy import sqrt
2248 >>> t2 = Triangle(Point(0, 0), Point(10, 0), Point(5, 5*sqrt(3)))
2249 >>> t2.is_equilateral()
2250 True
2252 """
2253 return not has_variety(s.length for s in self.sides)
2255 def is_isosceles(self):
2256 """Are two or more of the sides the same length?
2258 Returns
2259 =======
2261 is_isosceles : boolean
2263 See Also
2264 ========
2266 is_equilateral, is_right, is_scalene
2268 Examples
2269 ========
2271 >>> from sympy import Triangle, Point
2272 >>> t1 = Triangle(Point(0, 0), Point(4, 0), Point(2, 4))
2273 >>> t1.is_isosceles()
2274 True
2276 """
2277 return has_dups(s.length for s in self.sides)
2279 def is_scalene(self):
2280 """Are all the sides of the triangle of different lengths?
2282 Returns
2283 =======
2285 is_scalene : boolean
2287 See Also
2288 ========
2290 is_equilateral, is_isosceles, is_right
2292 Examples
2293 ========
2295 >>> from sympy import Triangle, Point
2296 >>> t1 = Triangle(Point(0, 0), Point(4, 0), Point(1, 4))
2297 >>> t1.is_scalene()
2298 True
2300 """
2301 return not has_dups(s.length for s in self.sides)
2303 def is_right(self):
2304 """Is the triangle right-angled.
2306 Returns
2307 =======
2309 is_right : boolean
2311 See Also
2312 ========
2314 sympy.geometry.line.LinearEntity.is_perpendicular
2315 is_equilateral, is_isosceles, is_scalene
2317 Examples
2318 ========
2320 >>> from sympy import Triangle, Point
2321 >>> t1 = Triangle(Point(0, 0), Point(4, 0), Point(4, 3))
2322 >>> t1.is_right()
2323 True
2325 """
2326 s = self.sides
2327 return Segment.is_perpendicular(s[0], s[1]) or \
2328 Segment.is_perpendicular(s[1], s[2]) or \
2329 Segment.is_perpendicular(s[0], s[2])
2331 @property
2332 def altitudes(self):
2333 """The altitudes of the triangle.
2335 An altitude of a triangle is a segment through a vertex,
2336 perpendicular to the opposite side, with length being the
2337 height of the vertex measured from the line containing the side.
2339 Returns
2340 =======
2342 altitudes : dict
2343 The dictionary consists of keys which are vertices and values
2344 which are Segments.
2346 See Also
2347 ========
2349 sympy.geometry.point.Point, sympy.geometry.line.Segment.length
2351 Examples
2352 ========
2354 >>> from sympy import Point, Triangle
2355 >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1)
2356 >>> t = Triangle(p1, p2, p3)
2357 >>> t.altitudes[p1]
2358 Segment2D(Point2D(0, 0), Point2D(1/2, 1/2))
2360 """
2361 s = self.sides
2362 v = self.vertices
2363 return {v[0]: s[1].perpendicular_segment(v[0]),
2364 v[1]: s[2].perpendicular_segment(v[1]),
2365 v[2]: s[0].perpendicular_segment(v[2])}
2367 @property
2368 def orthocenter(self):
2369 """The orthocenter of the triangle.
2371 The orthocenter is the intersection of the altitudes of a triangle.
2372 It may lie inside, outside or on the triangle.
2374 Returns
2375 =======
2377 orthocenter : Point
2379 See Also
2380 ========
2382 sympy.geometry.point.Point
2384 Examples
2385 ========
2387 >>> from sympy import Point, Triangle
2388 >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1)
2389 >>> t = Triangle(p1, p2, p3)
2390 >>> t.orthocenter
2391 Point2D(0, 0)
2393 """
2394 a = self.altitudes
2395 v = self.vertices
2396 return Line(a[v[0]]).intersection(Line(a[v[1]]))[0]
2398 @property
2399 def circumcenter(self):
2400 """The circumcenter of the triangle
2402 The circumcenter is the center of the circumcircle.
2404 Returns
2405 =======
2407 circumcenter : Point
2409 See Also
2410 ========
2412 sympy.geometry.point.Point
2414 Examples
2415 ========
2417 >>> from sympy import Point, Triangle
2418 >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1)
2419 >>> t = Triangle(p1, p2, p3)
2420 >>> t.circumcenter
2421 Point2D(1/2, 1/2)
2422 """
2423 a, b, c = [x.perpendicular_bisector() for x in self.sides]
2424 return a.intersection(b)[0]
2426 @property
2427 def circumradius(self):
2428 """The radius of the circumcircle of the triangle.
2430 Returns
2431 =======
2433 circumradius : number of Basic instance
2435 See Also
2436 ========
2438 sympy.geometry.ellipse.Circle.radius
2440 Examples
2441 ========
2443 >>> from sympy import Symbol
2444 >>> from sympy import Point, Triangle
2445 >>> a = Symbol('a')
2446 >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, a)
2447 >>> t = Triangle(p1, p2, p3)
2448 >>> t.circumradius
2449 sqrt(a**2/4 + 1/4)
2450 """
2451 return Point.distance(self.circumcenter, self.vertices[0])
2453 @property
2454 def circumcircle(self):
2455 """The circle which passes through the three vertices of the triangle.
2457 Returns
2458 =======
2460 circumcircle : Circle
2462 See Also
2463 ========
2465 sympy.geometry.ellipse.Circle
2467 Examples
2468 ========
2470 >>> from sympy import Point, Triangle
2471 >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1)
2472 >>> t = Triangle(p1, p2, p3)
2473 >>> t.circumcircle
2474 Circle(Point2D(1/2, 1/2), sqrt(2)/2)
2476 """
2477 return Circle(self.circumcenter, self.circumradius)
2479 def bisectors(self):
2480 """The angle bisectors of the triangle.
2482 An angle bisector of a triangle is a straight line through a vertex
2483 which cuts the corresponding angle in half.
2485 Returns
2486 =======
2488 bisectors : dict
2489 Each key is a vertex (Point) and each value is the corresponding
2490 bisector (Segment).
2492 See Also
2493 ========
2495 sympy.geometry.point.Point, sympy.geometry.line.Segment
2497 Examples
2498 ========
2500 >>> from sympy import Point, Triangle, Segment
2501 >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1)
2502 >>> t = Triangle(p1, p2, p3)
2503 >>> from sympy import sqrt
2504 >>> t.bisectors()[p2] == Segment(Point(1, 0), Point(0, sqrt(2) - 1))
2505 True
2507 """
2508 # use lines containing sides so containment check during
2509 # intersection calculation can be avoided, thus reducing
2510 # the processing time for calculating the bisectors
2511 s = [Line(l) for l in self.sides]
2512 v = self.vertices
2513 c = self.incenter
2514 l1 = Segment(v[0], Line(v[0], c).intersection(s[1])[0])
2515 l2 = Segment(v[1], Line(v[1], c).intersection(s[2])[0])
2516 l3 = Segment(v[2], Line(v[2], c).intersection(s[0])[0])
2517 return {v[0]: l1, v[1]: l2, v[2]: l3}
2519 @property
2520 def incenter(self):
2521 """The center of the incircle.
2523 The incircle is the circle which lies inside the triangle and touches
2524 all three sides.
2526 Returns
2527 =======
2529 incenter : Point
2531 See Also
2532 ========
2534 incircle, sympy.geometry.point.Point
2536 Examples
2537 ========
2539 >>> from sympy import Point, Triangle
2540 >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1)
2541 >>> t = Triangle(p1, p2, p3)
2542 >>> t.incenter
2543 Point2D(1 - sqrt(2)/2, 1 - sqrt(2)/2)
2545 """
2546 s = self.sides
2547 l = Matrix([s[i].length for i in [1, 2, 0]])
2548 p = sum(l)
2549 v = self.vertices
2550 x = simplify(l.dot(Matrix([vi.x for vi in v]))/p)
2551 y = simplify(l.dot(Matrix([vi.y for vi in v]))/p)
2552 return Point(x, y)
2554 @property
2555 def inradius(self):
2556 """The radius of the incircle.
2558 Returns
2559 =======
2561 inradius : number of Basic instance
2563 See Also
2564 ========
2566 incircle, sympy.geometry.ellipse.Circle.radius
2568 Examples
2569 ========
2571 >>> from sympy import Point, Triangle
2572 >>> p1, p2, p3 = Point(0, 0), Point(4, 0), Point(0, 3)
2573 >>> t = Triangle(p1, p2, p3)
2574 >>> t.inradius
2575 1
2577 """
2578 return simplify(2 * self.area / self.perimeter)
2580 @property
2581 def incircle(self):
2582 """The incircle of the triangle.
2584 The incircle is the circle which lies inside the triangle and touches
2585 all three sides.
2587 Returns
2588 =======
2590 incircle : Circle
2592 See Also
2593 ========
2595 sympy.geometry.ellipse.Circle
2597 Examples
2598 ========
2600 >>> from sympy import Point, Triangle
2601 >>> p1, p2, p3 = Point(0, 0), Point(2, 0), Point(0, 2)
2602 >>> t = Triangle(p1, p2, p3)
2603 >>> t.incircle
2604 Circle(Point2D(2 - sqrt(2), 2 - sqrt(2)), 2 - sqrt(2))
2606 """
2607 return Circle(self.incenter, self.inradius)
2609 @property
2610 def exradii(self):
2611 """The radius of excircles of a triangle.
2613 An excircle of the triangle is a circle lying outside the triangle,
2614 tangent to one of its sides and tangent to the extensions of the
2615 other two.
2617 Returns
2618 =======
2620 exradii : dict
2622 See Also
2623 ========
2625 sympy.geometry.polygon.Triangle.inradius
2627 Examples
2628 ========
2630 The exradius touches the side of the triangle to which it is keyed, e.g.
2631 the exradius touching side 2 is:
2633 >>> from sympy import Point, Triangle
2634 >>> p1, p2, p3 = Point(0, 0), Point(6, 0), Point(0, 2)
2635 >>> t = Triangle(p1, p2, p3)
2636 >>> t.exradii[t.sides[2]]
2637 -2 + sqrt(10)
2639 References
2640 ==========
2642 .. [1] https://mathworld.wolfram.com/Exradius.html
2643 .. [2] https://mathworld.wolfram.com/Excircles.html
2645 """
2647 side = self.sides
2648 a = side[0].length
2649 b = side[1].length
2650 c = side[2].length
2651 s = (a+b+c)/2
2652 area = self.area
2653 exradii = {self.sides[0]: simplify(area/(s-a)),
2654 self.sides[1]: simplify(area/(s-b)),
2655 self.sides[2]: simplify(area/(s-c))}
2657 return exradii
2659 @property
2660 def excenters(self):
2661 """Excenters of the triangle.
2663 An excenter is the center of a circle that is tangent to a side of the
2664 triangle and the extensions of the other two sides.
2666 Returns
2667 =======
2669 excenters : dict
2672 Examples
2673 ========
2675 The excenters are keyed to the side of the triangle to which their corresponding
2676 excircle is tangent: The center is keyed, e.g. the excenter of a circle touching
2677 side 0 is:
2679 >>> from sympy import Point, Triangle
2680 >>> p1, p2, p3 = Point(0, 0), Point(6, 0), Point(0, 2)
2681 >>> t = Triangle(p1, p2, p3)
2682 >>> t.excenters[t.sides[0]]
2683 Point2D(12*sqrt(10), 2/3 + sqrt(10)/3)
2685 See Also
2686 ========
2688 sympy.geometry.polygon.Triangle.exradii
2690 References
2691 ==========
2693 .. [1] https://mathworld.wolfram.com/Excircles.html
2695 """
2697 s = self.sides
2698 v = self.vertices
2699 a = s[0].length
2700 b = s[1].length
2701 c = s[2].length
2702 x = [v[0].x, v[1].x, v[2].x]
2703 y = [v[0].y, v[1].y, v[2].y]
2705 exc_coords = {
2706 "x1": simplify(-a*x[0]+b*x[1]+c*x[2]/(-a+b+c)),
2707 "x2": simplify(a*x[0]-b*x[1]+c*x[2]/(a-b+c)),
2708 "x3": simplify(a*x[0]+b*x[1]-c*x[2]/(a+b-c)),
2709 "y1": simplify(-a*y[0]+b*y[1]+c*y[2]/(-a+b+c)),
2710 "y2": simplify(a*y[0]-b*y[1]+c*y[2]/(a-b+c)),
2711 "y3": simplify(a*y[0]+b*y[1]-c*y[2]/(a+b-c))
2712 }
2714 excenters = {
2715 s[0]: Point(exc_coords["x1"], exc_coords["y1"]),
2716 s[1]: Point(exc_coords["x2"], exc_coords["y2"]),
2717 s[2]: Point(exc_coords["x3"], exc_coords["y3"])
2718 }
2720 return excenters
2722 @property
2723 def medians(self):
2724 """The medians of the triangle.
2726 A median of a triangle is a straight line through a vertex and the
2727 midpoint of the opposite side, and divides the triangle into two
2728 equal areas.
2730 Returns
2731 =======
2733 medians : dict
2734 Each key is a vertex (Point) and each value is the median (Segment)
2735 at that point.
2737 See Also
2738 ========
2740 sympy.geometry.point.Point.midpoint, sympy.geometry.line.Segment.midpoint
2742 Examples
2743 ========
2745 >>> from sympy import Point, Triangle
2746 >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1)
2747 >>> t = Triangle(p1, p2, p3)
2748 >>> t.medians[p1]
2749 Segment2D(Point2D(0, 0), Point2D(1/2, 1/2))
2751 """
2752 s = self.sides
2753 v = self.vertices
2754 return {v[0]: Segment(v[0], s[1].midpoint),
2755 v[1]: Segment(v[1], s[2].midpoint),
2756 v[2]: Segment(v[2], s[0].midpoint)}
2758 @property
2759 def medial(self):
2760 """The medial triangle of the triangle.
2762 The triangle which is formed from the midpoints of the three sides.
2764 Returns
2765 =======
2767 medial : Triangle
2769 See Also
2770 ========
2772 sympy.geometry.line.Segment.midpoint
2774 Examples
2775 ========
2777 >>> from sympy import Point, Triangle
2778 >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1)
2779 >>> t = Triangle(p1, p2, p3)
2780 >>> t.medial
2781 Triangle(Point2D(1/2, 0), Point2D(1/2, 1/2), Point2D(0, 1/2))
2783 """
2784 s = self.sides
2785 return Triangle(s[0].midpoint, s[1].midpoint, s[2].midpoint)
2787 @property
2788 def nine_point_circle(self):
2789 """The nine-point circle of the triangle.
2791 Nine-point circle is the circumcircle of the medial triangle, which
2792 passes through the feet of altitudes and the middle points of segments
2793 connecting the vertices and the orthocenter.
2795 Returns
2796 =======
2798 nine_point_circle : Circle
2800 See also
2801 ========
2803 sympy.geometry.line.Segment.midpoint
2804 sympy.geometry.polygon.Triangle.medial
2805 sympy.geometry.polygon.Triangle.orthocenter
2807 Examples
2808 ========
2810 >>> from sympy import Point, Triangle
2811 >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1)
2812 >>> t = Triangle(p1, p2, p3)
2813 >>> t.nine_point_circle
2814 Circle(Point2D(1/4, 1/4), sqrt(2)/4)
2816 """
2817 return Circle(*self.medial.vertices)
2819 @property
2820 def eulerline(self):
2821 """The Euler line of the triangle.
2823 The line which passes through circumcenter, centroid and orthocenter.
2825 Returns
2826 =======
2828 eulerline : Line (or Point for equilateral triangles in which case all
2829 centers coincide)
2831 Examples
2832 ========
2834 >>> from sympy import Point, Triangle
2835 >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1)
2836 >>> t = Triangle(p1, p2, p3)
2837 >>> t.eulerline
2838 Line2D(Point2D(0, 0), Point2D(1/2, 1/2))
2840 """
2841 if self.is_equilateral():
2842 return self.orthocenter
2843 return Line(self.orthocenter, self.circumcenter)
2845def rad(d):
2846 """Return the radian value for the given degrees (pi = 180 degrees)."""
2847 return d*pi/180
2850def deg(r):
2851 """Return the degree value for the given radians (pi = 180 degrees)."""
2852 return r/pi*180
2855def _slope(d):
2856 rv = tan(rad(d))
2857 return rv
2860def _asa(d1, l, d2):
2861 """Return triangle having side with length l on the x-axis."""
2862 xy = Line((0, 0), slope=_slope(d1)).intersection(
2863 Line((l, 0), slope=_slope(180 - d2)))[0]
2864 return Triangle((0, 0), (l, 0), xy)
2867def _sss(l1, l2, l3):
2868 """Return triangle having side of length l1 on the x-axis."""
2869 c1 = Circle((0, 0), l3)
2870 c2 = Circle((l1, 0), l2)
2871 inter = [a for a in c1.intersection(c2) if a.y.is_nonnegative]
2872 if not inter:
2873 return None
2874 pt = inter[0]
2875 return Triangle((0, 0), (l1, 0), pt)
2878def _sas(l1, d, l2):
2879 """Return triangle having side with length l2 on the x-axis."""
2880 p1 = Point(0, 0)
2881 p2 = Point(l2, 0)
2882 p3 = Point(cos(rad(d))*l1, sin(rad(d))*l1)
2883 return Triangle(p1, p2, p3)