Spaces:
Sleeping
Sleeping
| from sympy.core import Expr, S, oo, pi, sympify | |
| from sympy.core.evalf import N | |
| from sympy.core.sorting import default_sort_key, ordered | |
| from sympy.core.symbol import _symbol, Dummy, Symbol | |
| from sympy.functions.elementary.complexes import sign | |
| from sympy.functions.elementary.piecewise import Piecewise | |
| from sympy.functions.elementary.trigonometric import cos, sin, tan | |
| from .ellipse import Circle | |
| from .entity import GeometryEntity, GeometrySet | |
| from .exceptions import GeometryError | |
| from .line import Line, Segment, Ray | |
| from .point import Point | |
| from sympy.logic import And | |
| from sympy.matrices import Matrix | |
| from sympy.simplify.simplify import simplify | |
| from sympy.solvers.solvers import solve | |
| from sympy.utilities.iterables import has_dups, has_variety, uniq, rotate_left, least_rotation | |
| from sympy.utilities.misc import as_int, func_name | |
| from mpmath.libmp.libmpf import prec_to_dps | |
| import warnings | |
| x, y, T = [Dummy('polygon_dummy', real=True) for i in range(3)] | |
| class Polygon(GeometrySet): | |
| """A two-dimensional polygon. | |
| A simple polygon in space. Can be constructed from a sequence of points | |
| or from a center, radius, number of sides and rotation angle. | |
| Parameters | |
| ========== | |
| vertices | |
| A sequence of points. | |
| n : int, optional | |
| If $> 0$, an n-sided RegularPolygon is created. | |
| Default value is $0$. | |
| Attributes | |
| ========== | |
| area | |
| angles | |
| perimeter | |
| vertices | |
| centroid | |
| sides | |
| Raises | |
| ====== | |
| GeometryError | |
| If all parameters are not Points. | |
| See Also | |
| ======== | |
| sympy.geometry.point.Point, sympy.geometry.line.Segment, Triangle | |
| Notes | |
| ===== | |
| Polygons are treated as closed paths rather than 2D areas so | |
| some calculations can be be negative or positive (e.g., area) | |
| based on the orientation of the points. | |
| Any consecutive identical points are reduced to a single point | |
| and any points collinear and between two points will be removed | |
| unless they are needed to define an explicit intersection (see examples). | |
| A Triangle, Segment or Point will be returned when there are 3 or | |
| fewer points provided. | |
| Examples | |
| ======== | |
| >>> from sympy import Polygon, pi | |
| >>> p1, p2, p3, p4, p5 = [(0, 0), (1, 0), (5, 1), (0, 1), (3, 0)] | |
| >>> Polygon(p1, p2, p3, p4) | |
| Polygon(Point2D(0, 0), Point2D(1, 0), Point2D(5, 1), Point2D(0, 1)) | |
| >>> Polygon(p1, p2) | |
| Segment2D(Point2D(0, 0), Point2D(1, 0)) | |
| >>> Polygon(p1, p2, p5) | |
| Segment2D(Point2D(0, 0), Point2D(3, 0)) | |
| The area of a polygon is calculated as positive when vertices are | |
| traversed in a ccw direction. When the sides of a polygon cross the | |
| area will have positive and negative contributions. The following | |
| defines a Z shape where the bottom right connects back to the top | |
| left. | |
| >>> Polygon((0, 2), (2, 2), (0, 0), (2, 0)).area | |
| 0 | |
| When the keyword `n` is used to define the number of sides of the | |
| Polygon then a RegularPolygon is created and the other arguments are | |
| interpreted as center, radius and rotation. The unrotated RegularPolygon | |
| will always have a vertex at Point(r, 0) where `r` is the radius of the | |
| circle that circumscribes the RegularPolygon. Its method `spin` can be | |
| used to increment that angle. | |
| >>> p = Polygon((0,0), 1, n=3) | |
| >>> p | |
| RegularPolygon(Point2D(0, 0), 1, 3, 0) | |
| >>> p.vertices[0] | |
| Point2D(1, 0) | |
| >>> p.args[0] | |
| Point2D(0, 0) | |
| >>> p.spin(pi/2) | |
| >>> p.vertices[0] | |
| Point2D(0, 1) | |
| """ | |
| __slots__ = () | |
| def __new__(cls, *args, n = 0, **kwargs): | |
| if n: | |
| args = list(args) | |
| # return a virtual polygon with n sides | |
| if len(args) == 2: # center, radius | |
| args.append(n) | |
| elif len(args) == 3: # center, radius, rotation | |
| args.insert(2, n) | |
| return RegularPolygon(*args, **kwargs) | |
| vertices = [Point(a, dim=2, **kwargs) for a in args] | |
| # remove consecutive duplicates | |
| nodup = [] | |
| for p in vertices: | |
| if nodup and p == nodup[-1]: | |
| continue | |
| nodup.append(p) | |
| if len(nodup) > 1 and nodup[-1] == nodup[0]: | |
| nodup.pop() # last point was same as first | |
| # remove collinear points | |
| i = -3 | |
| while i < len(nodup) - 3 and len(nodup) > 2: | |
| a, b, c = nodup[i], nodup[i + 1], nodup[i + 2] | |
| if Point.is_collinear(a, b, c): | |
| nodup.pop(i + 1) | |
| if a == c: | |
| nodup.pop(i) | |
| else: | |
| i += 1 | |
| vertices = list(nodup) | |
| if len(vertices) > 3: | |
| return GeometryEntity.__new__(cls, *vertices, **kwargs) | |
| elif len(vertices) == 3: | |
| return Triangle(*vertices, **kwargs) | |
| elif len(vertices) == 2: | |
| return Segment(*vertices, **kwargs) | |
| else: | |
| return Point(*vertices, **kwargs) | |
| def area(self): | |
| """ | |
| The area of the polygon. | |
| Notes | |
| ===== | |
| The area calculation can be positive or negative based on the | |
| orientation of the points. If any side of the polygon crosses | |
| any other side, there will be areas having opposite signs. | |
| See Also | |
| ======== | |
| sympy.geometry.ellipse.Ellipse.area | |
| Examples | |
| ======== | |
| >>> from sympy import Point, Polygon | |
| >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)]) | |
| >>> poly = Polygon(p1, p2, p3, p4) | |
| >>> poly.area | |
| 3 | |
| In the Z shaped polygon (with the lower right connecting back | |
| to the upper left) the areas cancel out: | |
| >>> Z = Polygon((0, 1), (1, 1), (0, 0), (1, 0)) | |
| >>> Z.area | |
| 0 | |
| In the M shaped polygon, areas do not cancel because no side | |
| crosses any other (though there is a point of contact). | |
| >>> M = Polygon((0, 0), (0, 1), (2, 0), (3, 1), (3, 0)) | |
| >>> M.area | |
| -3/2 | |
| """ | |
| area = 0 | |
| args = self.args | |
| for i in range(len(args)): | |
| x1, y1 = args[i - 1].args | |
| x2, y2 = args[i].args | |
| area += x1*y2 - x2*y1 | |
| return simplify(area) / 2 | |
| def _is_clockwise(a, b, c): | |
| """Return True/False for cw/ccw orientation. | |
| Examples | |
| ======== | |
| >>> from sympy import Point, Polygon | |
| >>> a, b, c = [Point(i) for i in [(0, 0), (1, 1), (1, 0)]] | |
| >>> Polygon._is_clockwise(a, b, c) | |
| True | |
| >>> Polygon._is_clockwise(a, c, b) | |
| False | |
| """ | |
| ba = b - a | |
| ca = c - a | |
| t_area = simplify(ba.x*ca.y - ca.x*ba.y) | |
| res = t_area.is_nonpositive | |
| if res is None: | |
| raise ValueError("Can't determine orientation") | |
| return res | |
| def angles(self): | |
| """The internal angle at each vertex. | |
| Returns | |
| ======= | |
| angles : dict | |
| A dictionary where each key is a vertex and each value is the | |
| internal angle at that vertex. The vertices are represented as | |
| Points. | |
| See Also | |
| ======== | |
| sympy.geometry.point.Point, sympy.geometry.line.LinearEntity.angle_between | |
| Examples | |
| ======== | |
| >>> from sympy import Point, Polygon | |
| >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)]) | |
| >>> poly = Polygon(p1, p2, p3, p4) | |
| >>> poly.angles[p1] | |
| pi/2 | |
| >>> poly.angles[p2] | |
| acos(-4*sqrt(17)/17) | |
| """ | |
| args = self.vertices | |
| n = len(args) | |
| ret = {} | |
| for i in range(n): | |
| a, b, c = args[i - 2], args[i - 1], args[i] | |
| reflex_ang = Ray(b, a).angle_between(Ray(b, c)) | |
| if self._is_clockwise(a, b, c): | |
| ret[b] = 2*S.Pi - reflex_ang | |
| else: | |
| ret[b] = reflex_ang | |
| # internal sum should be pi*(n - 2), not pi*(n+2) | |
| # so if ratio is (n+2)/(n-2) > 1 it is wrong | |
| wrong = ((sum(ret.values())/S.Pi-1)/(n - 2) - 1).is_positive | |
| if wrong: | |
| two_pi = 2*S.Pi | |
| for b in ret: | |
| ret[b] = two_pi - ret[b] | |
| elif wrong is None: | |
| raise ValueError("could not determine Polygon orientation.") | |
| return ret | |
| def ambient_dimension(self): | |
| return self.vertices[0].ambient_dimension | |
| def perimeter(self): | |
| """The perimeter of the polygon. | |
| Returns | |
| ======= | |
| perimeter : number or Basic instance | |
| See Also | |
| ======== | |
| sympy.geometry.line.Segment.length | |
| Examples | |
| ======== | |
| >>> from sympy import Point, Polygon | |
| >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)]) | |
| >>> poly = Polygon(p1, p2, p3, p4) | |
| >>> poly.perimeter | |
| sqrt(17) + 7 | |
| """ | |
| p = 0 | |
| args = self.vertices | |
| for i in range(len(args)): | |
| p += args[i - 1].distance(args[i]) | |
| return simplify(p) | |
| def vertices(self): | |
| """The vertices of the polygon. | |
| Returns | |
| ======= | |
| vertices : list of Points | |
| Notes | |
| ===== | |
| When iterating over the vertices, it is more efficient to index self | |
| rather than to request the vertices and index them. Only use the | |
| vertices when you want to process all of them at once. This is even | |
| more important with RegularPolygons that calculate each vertex. | |
| See Also | |
| ======== | |
| sympy.geometry.point.Point | |
| Examples | |
| ======== | |
| >>> from sympy import Point, Polygon | |
| >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)]) | |
| >>> poly = Polygon(p1, p2, p3, p4) | |
| >>> poly.vertices | |
| [Point2D(0, 0), Point2D(1, 0), Point2D(5, 1), Point2D(0, 1)] | |
| >>> poly.vertices[0] | |
| Point2D(0, 0) | |
| """ | |
| return list(self.args) | |
| def centroid(self): | |
| """The centroid of the polygon. | |
| Returns | |
| ======= | |
| centroid : Point | |
| See Also | |
| ======== | |
| sympy.geometry.point.Point, sympy.geometry.util.centroid | |
| Examples | |
| ======== | |
| >>> from sympy import Point, Polygon | |
| >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)]) | |
| >>> poly = Polygon(p1, p2, p3, p4) | |
| >>> poly.centroid | |
| Point2D(31/18, 11/18) | |
| """ | |
| A = 1/(6*self.area) | |
| cx, cy = 0, 0 | |
| args = self.args | |
| for i in range(len(args)): | |
| x1, y1 = args[i - 1].args | |
| x2, y2 = args[i].args | |
| v = x1*y2 - x2*y1 | |
| cx += v*(x1 + x2) | |
| cy += v*(y1 + y2) | |
| return Point(simplify(A*cx), simplify(A*cy)) | |
| def second_moment_of_area(self, point=None): | |
| """Returns the second moment and product moment of area of a two dimensional polygon. | |
| Parameters | |
| ========== | |
| point : Point, two-tuple of sympifyable objects, or None(default=None) | |
| point is the point about which second moment of area is to be found. | |
| If "point=None" it will be calculated about the axis passing through the | |
| centroid of the polygon. | |
| Returns | |
| ======= | |
| I_xx, I_yy, I_xy : number or SymPy expression | |
| I_xx, I_yy are second moment of area of a two dimensional polygon. | |
| I_xy is product moment of area of a two dimensional polygon. | |
| Examples | |
| ======== | |
| >>> from sympy import Polygon, symbols | |
| >>> a, b = symbols('a, b') | |
| >>> p1, p2, p3, p4, p5 = [(0, 0), (a, 0), (a, b), (0, b), (a/3, b/3)] | |
| >>> rectangle = Polygon(p1, p2, p3, p4) | |
| >>> rectangle.second_moment_of_area() | |
| (a*b**3/12, a**3*b/12, 0) | |
| >>> rectangle.second_moment_of_area(p5) | |
| (a*b**3/9, a**3*b/9, a**2*b**2/36) | |
| References | |
| ========== | |
| .. [1] https://en.wikipedia.org/wiki/Second_moment_of_area | |
| """ | |
| I_xx, I_yy, I_xy = 0, 0, 0 | |
| args = self.vertices | |
| for i in range(len(args)): | |
| x1, y1 = args[i-1].args | |
| x2, y2 = args[i].args | |
| v = x1*y2 - x2*y1 | |
| I_xx += (y1**2 + y1*y2 + y2**2)*v | |
| I_yy += (x1**2 + x1*x2 + x2**2)*v | |
| I_xy += (x1*y2 + 2*x1*y1 + 2*x2*y2 + x2*y1)*v | |
| A = self.area | |
| c_x = self.centroid[0] | |
| c_y = self.centroid[1] | |
| # parallel axis theorem | |
| I_xx_c = (I_xx/12) - (A*(c_y**2)) | |
| I_yy_c = (I_yy/12) - (A*(c_x**2)) | |
| I_xy_c = (I_xy/24) - (A*(c_x*c_y)) | |
| if point is None: | |
| return I_xx_c, I_yy_c, I_xy_c | |
| I_xx = (I_xx_c + A*((point[1]-c_y)**2)) | |
| I_yy = (I_yy_c + A*((point[0]-c_x)**2)) | |
| I_xy = (I_xy_c + A*((point[0]-c_x)*(point[1]-c_y))) | |
| return I_xx, I_yy, I_xy | |
| def first_moment_of_area(self, point=None): | |
| """ | |
| Returns the first moment of area of a two-dimensional polygon with | |
| respect to a certain point of interest. | |
| First moment of area is a measure of the distribution of the area | |
| of a polygon in relation to an axis. The first moment of area of | |
| the entire polygon about its own centroid is always zero. Therefore, | |
| here it is calculated for an area, above or below a certain point | |
| of interest, that makes up a smaller portion of the polygon. This | |
| area is bounded by the point of interest and the extreme end | |
| (top or bottom) of the polygon. The first moment for this area is | |
| is then determined about the centroidal axis of the initial polygon. | |
| References | |
| ========== | |
| .. [1] https://skyciv.com/docs/tutorials/section-tutorials/calculating-the-statical-or-first-moment-of-area-of-beam-sections/?cc=BMD | |
| .. [2] https://mechanicalc.com/reference/cross-sections | |
| Parameters | |
| ========== | |
| point: Point, two-tuple of sympifyable objects, or None (default=None) | |
| point is the point above or below which the area of interest lies | |
| If ``point=None`` then the centroid acts as the point of interest. | |
| Returns | |
| ======= | |
| Q_x, Q_y: number or SymPy expressions | |
| Q_x is the first moment of area about the x-axis | |
| Q_y is the first moment of area about the y-axis | |
| A negative sign indicates that the section modulus is | |
| determined for a section below (or left of) the centroidal axis | |
| Examples | |
| ======== | |
| >>> from sympy import Point, Polygon | |
| >>> a, b = 50, 10 | |
| >>> p1, p2, p3, p4 = [(0, b), (0, 0), (a, 0), (a, b)] | |
| >>> p = Polygon(p1, p2, p3, p4) | |
| >>> p.first_moment_of_area() | |
| (625, 3125) | |
| >>> p.first_moment_of_area(point=Point(30, 7)) | |
| (525, 3000) | |
| """ | |
| if point: | |
| xc, yc = self.centroid | |
| else: | |
| point = self.centroid | |
| xc, yc = point | |
| h_line = Line(point, slope=0) | |
| v_line = Line(point, slope=S.Infinity) | |
| h_poly = self.cut_section(h_line) | |
| v_poly = self.cut_section(v_line) | |
| poly_1 = h_poly[0] if h_poly[0].area <= h_poly[1].area else h_poly[1] | |
| poly_2 = v_poly[0] if v_poly[0].area <= v_poly[1].area else v_poly[1] | |
| Q_x = (poly_1.centroid.y - yc)*poly_1.area | |
| Q_y = (poly_2.centroid.x - xc)*poly_2.area | |
| return Q_x, Q_y | |
| def polar_second_moment_of_area(self): | |
| """Returns the polar modulus of a two-dimensional polygon | |
| It is a constituent of the second moment of area, linked through | |
| the perpendicular axis theorem. While the planar second moment of | |
| area describes an object's resistance to deflection (bending) when | |
| subjected to a force applied to a plane parallel to the central | |
| axis, the polar second moment of area describes an object's | |
| resistance to deflection when subjected to a moment applied in a | |
| plane perpendicular to the object's central axis (i.e. parallel to | |
| the cross-section) | |
| Examples | |
| ======== | |
| >>> from sympy import Polygon, symbols | |
| >>> a, b = symbols('a, b') | |
| >>> rectangle = Polygon((0, 0), (a, 0), (a, b), (0, b)) | |
| >>> rectangle.polar_second_moment_of_area() | |
| a**3*b/12 + a*b**3/12 | |
| References | |
| ========== | |
| .. [1] https://en.wikipedia.org/wiki/Polar_moment_of_inertia | |
| """ | |
| second_moment = self.second_moment_of_area() | |
| return second_moment[0] + second_moment[1] | |
| def section_modulus(self, point=None): | |
| """Returns a tuple with the section modulus of a two-dimensional | |
| polygon. | |
| Section modulus is a geometric property of a polygon defined as the | |
| ratio of second moment of area to the distance of the extreme end of | |
| the polygon from the centroidal axis. | |
| Parameters | |
| ========== | |
| point : Point, two-tuple of sympifyable objects, or None(default=None) | |
| point is the point at which section modulus is to be found. | |
| If "point=None" it will be calculated for the point farthest from the | |
| centroidal axis of the polygon. | |
| Returns | |
| ======= | |
| S_x, S_y: numbers or SymPy expressions | |
| S_x is the section modulus with respect to the x-axis | |
| S_y is the section modulus with respect to the y-axis | |
| A negative sign indicates that the section modulus is | |
| determined for a point below the centroidal axis | |
| Examples | |
| ======== | |
| >>> from sympy import symbols, Polygon, Point | |
| >>> a, b = symbols('a, b', positive=True) | |
| >>> rectangle = Polygon((0, 0), (a, 0), (a, b), (0, b)) | |
| >>> rectangle.section_modulus() | |
| (a*b**2/6, a**2*b/6) | |
| >>> rectangle.section_modulus(Point(a/4, b/4)) | |
| (-a*b**2/3, -a**2*b/3) | |
| References | |
| ========== | |
| .. [1] https://en.wikipedia.org/wiki/Section_modulus | |
| """ | |
| x_c, y_c = self.centroid | |
| if point is None: | |
| # taking x and y as maximum distances from centroid | |
| x_min, y_min, x_max, y_max = self.bounds | |
| y = max(y_c - y_min, y_max - y_c) | |
| x = max(x_c - x_min, x_max - x_c) | |
| else: | |
| # taking x and y as distances of the given point from the centroid | |
| y = point.y - y_c | |
| x = point.x - x_c | |
| second_moment= self.second_moment_of_area() | |
| S_x = second_moment[0]/y | |
| S_y = second_moment[1]/x | |
| return S_x, S_y | |
| def sides(self): | |
| """The directed line segments that form the sides of the polygon. | |
| Returns | |
| ======= | |
| sides : list of sides | |
| Each side is a directed Segment. | |
| See Also | |
| ======== | |
| sympy.geometry.point.Point, sympy.geometry.line.Segment | |
| Examples | |
| ======== | |
| >>> from sympy import Point, Polygon | |
| >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)]) | |
| >>> poly = Polygon(p1, p2, p3, p4) | |
| >>> poly.sides | |
| [Segment2D(Point2D(0, 0), Point2D(1, 0)), | |
| Segment2D(Point2D(1, 0), Point2D(5, 1)), | |
| Segment2D(Point2D(5, 1), Point2D(0, 1)), Segment2D(Point2D(0, 1), Point2D(0, 0))] | |
| """ | |
| res = [] | |
| args = self.vertices | |
| for i in range(-len(args), 0): | |
| res.append(Segment(args[i], args[i + 1])) | |
| return res | |
| def bounds(self): | |
| """Return a tuple (xmin, ymin, xmax, ymax) representing the bounding | |
| rectangle for the geometric figure. | |
| """ | |
| verts = self.vertices | |
| xs = [p.x for p in verts] | |
| ys = [p.y for p in verts] | |
| return (min(xs), min(ys), max(xs), max(ys)) | |
| def is_convex(self): | |
| """Is the polygon convex? | |
| A polygon is convex if all its interior angles are less than 180 | |
| degrees and there are no intersections between sides. | |
| Returns | |
| ======= | |
| is_convex : boolean | |
| True if this polygon is convex, False otherwise. | |
| See Also | |
| ======== | |
| sympy.geometry.util.convex_hull | |
| Examples | |
| ======== | |
| >>> from sympy import Point, Polygon | |
| >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)]) | |
| >>> poly = Polygon(p1, p2, p3, p4) | |
| >>> poly.is_convex() | |
| True | |
| """ | |
| # Determine orientation of points | |
| args = self.vertices | |
| cw = self._is_clockwise(args[-2], args[-1], args[0]) | |
| for i in range(1, len(args)): | |
| if cw ^ self._is_clockwise(args[i - 2], args[i - 1], args[i]): | |
| return False | |
| # check for intersecting sides | |
| sides = self.sides | |
| for i, si in enumerate(sides): | |
| pts = si.args | |
| # exclude the sides connected to si | |
| for j in range(1 if i == len(sides) - 1 else 0, i - 1): | |
| sj = sides[j] | |
| if sj.p1 not in pts and sj.p2 not in pts: | |
| hit = si.intersection(sj) | |
| if hit: | |
| return False | |
| return True | |
| def encloses_point(self, p): | |
| """ | |
| Return True if p is enclosed by (is inside of) self. | |
| Notes | |
| ===== | |
| Being on the border of self is considered False. | |
| Parameters | |
| ========== | |
| p : Point | |
| Returns | |
| ======= | |
| encloses_point : True, False or None | |
| See Also | |
| ======== | |
| sympy.geometry.point.Point, sympy.geometry.ellipse.Ellipse.encloses_point | |
| Examples | |
| ======== | |
| >>> from sympy import Polygon, Point | |
| >>> p = Polygon((0, 0), (4, 0), (4, 4)) | |
| >>> p.encloses_point(Point(2, 1)) | |
| True | |
| >>> p.encloses_point(Point(2, 2)) | |
| False | |
| >>> p.encloses_point(Point(5, 5)) | |
| False | |
| References | |
| ========== | |
| .. [1] https://paulbourke.net/geometry/polygonmesh/#insidepoly | |
| """ | |
| p = Point(p, dim=2) | |
| if p in self.vertices or any(p in s for s in self.sides): | |
| return False | |
| # move to p, checking that the result is numeric | |
| lit = [] | |
| for v in self.vertices: | |
| lit.append(v - p) # the difference is simplified | |
| if lit[-1].free_symbols: | |
| return None | |
| poly = Polygon(*lit) | |
| # polygon closure is assumed in the following test but Polygon removes duplicate pts so | |
| # the last point has to be added so all sides are computed. Using Polygon.sides is | |
| # not good since Segments are unordered. | |
| args = poly.args | |
| indices = list(range(-len(args), 1)) | |
| if poly.is_convex(): | |
| orientation = None | |
| for i in indices: | |
| a = args[i] | |
| b = args[i + 1] | |
| test = ((-a.y)*(b.x - a.x) - (-a.x)*(b.y - a.y)).is_negative | |
| if orientation is None: | |
| orientation = test | |
| elif test is not orientation: | |
| return False | |
| return True | |
| hit_odd = False | |
| p1x, p1y = args[0].args | |
| for i in indices[1:]: | |
| p2x, p2y = args[i].args | |
| if 0 > min(p1y, p2y): | |
| if 0 <= max(p1y, p2y): | |
| if 0 <= max(p1x, p2x): | |
| if p1y != p2y: | |
| xinters = (-p1y)*(p2x - p1x)/(p2y - p1y) + p1x | |
| if p1x == p2x or 0 <= xinters: | |
| hit_odd = not hit_odd | |
| p1x, p1y = p2x, p2y | |
| return hit_odd | |
| def arbitrary_point(self, parameter='t'): | |
| """A parameterized point on the polygon. | |
| The parameter, varying from 0 to 1, assigns points to the position on | |
| the perimeter that is that fraction of the total perimeter. So the | |
| point evaluated at t=1/2 would return the point from the first vertex | |
| that is 1/2 way around the polygon. | |
| Parameters | |
| ========== | |
| parameter : str, optional | |
| Default value is 't'. | |
| Returns | |
| ======= | |
| arbitrary_point : Point | |
| Raises | |
| ====== | |
| ValueError | |
| When `parameter` already appears in the Polygon's definition. | |
| See Also | |
| ======== | |
| sympy.geometry.point.Point | |
| Examples | |
| ======== | |
| >>> from sympy import Polygon, Symbol | |
| >>> t = Symbol('t', real=True) | |
| >>> tri = Polygon((0, 0), (1, 0), (1, 1)) | |
| >>> p = tri.arbitrary_point('t') | |
| >>> perimeter = tri.perimeter | |
| >>> s1, s2 = [s.length for s in tri.sides[:2]] | |
| >>> p.subs(t, (s1 + s2/2)/perimeter) | |
| Point2D(1, 1/2) | |
| """ | |
| t = _symbol(parameter, real=True) | |
| if t.name in (f.name for f in self.free_symbols): | |
| raise ValueError('Symbol %s already appears in object and cannot be used as a parameter.' % t.name) | |
| sides = [] | |
| perimeter = self.perimeter | |
| perim_fraction_start = 0 | |
| for s in self.sides: | |
| side_perim_fraction = s.length/perimeter | |
| perim_fraction_end = perim_fraction_start + side_perim_fraction | |
| pt = s.arbitrary_point(parameter).subs( | |
| t, (t - perim_fraction_start)/side_perim_fraction) | |
| sides.append( | |
| (pt, (And(perim_fraction_start <= t, t < perim_fraction_end)))) | |
| perim_fraction_start = perim_fraction_end | |
| return Piecewise(*sides) | |
| def parameter_value(self, other, t): | |
| if not isinstance(other,GeometryEntity): | |
| other = Point(other, dim=self.ambient_dimension) | |
| if not isinstance(other,Point): | |
| raise ValueError("other must be a point") | |
| if other.free_symbols: | |
| raise NotImplementedError('non-numeric coordinates') | |
| unknown = False | |
| p = self.arbitrary_point(T) | |
| for pt, cond in p.args: | |
| sol = solve(pt - other, T, dict=True) | |
| if not sol: | |
| continue | |
| value = sol[0][T] | |
| if simplify(cond.subs(T, value)) == True: | |
| return {t: value} | |
| unknown = True | |
| if unknown: | |
| raise ValueError("Given point may not be on %s" % func_name(self)) | |
| raise ValueError("Given point is not on %s" % func_name(self)) | |
| def plot_interval(self, parameter='t'): | |
| """The plot interval for the default geometric plot of the polygon. | |
| Parameters | |
| ========== | |
| parameter : str, optional | |
| Default value is 't'. | |
| Returns | |
| ======= | |
| plot_interval : list (plot interval) | |
| [parameter, lower_bound, upper_bound] | |
| Examples | |
| ======== | |
| >>> from sympy import Polygon | |
| >>> p = Polygon((0, 0), (1, 0), (1, 1)) | |
| >>> p.plot_interval() | |
| [t, 0, 1] | |
| """ | |
| t = Symbol(parameter, real=True) | |
| return [t, 0, 1] | |
| def intersection(self, o): | |
| """The intersection of polygon and geometry entity. | |
| The intersection may be empty and can contain individual Points and | |
| complete Line Segments. | |
| Parameters | |
| ========== | |
| other: GeometryEntity | |
| Returns | |
| ======= | |
| intersection : list | |
| The list of Segments and Points | |
| See Also | |
| ======== | |
| sympy.geometry.point.Point, sympy.geometry.line.Segment | |
| Examples | |
| ======== | |
| >>> from sympy import Point, Polygon, Line | |
| >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)]) | |
| >>> poly1 = Polygon(p1, p2, p3, p4) | |
| >>> p5, p6, p7 = map(Point, [(3, 2), (1, -1), (0, 2)]) | |
| >>> poly2 = Polygon(p5, p6, p7) | |
| >>> poly1.intersection(poly2) | |
| [Point2D(1/3, 1), Point2D(2/3, 0), Point2D(9/5, 1/5), Point2D(7/3, 1)] | |
| >>> poly1.intersection(Line(p1, p2)) | |
| [Segment2D(Point2D(0, 0), Point2D(1, 0))] | |
| >>> poly1.intersection(p1) | |
| [Point2D(0, 0)] | |
| """ | |
| intersection_result = [] | |
| k = o.sides if isinstance(o, Polygon) else [o] | |
| for side in self.sides: | |
| for side1 in k: | |
| intersection_result.extend(side.intersection(side1)) | |
| intersection_result = list(uniq(intersection_result)) | |
| points = [entity for entity in intersection_result if isinstance(entity, Point)] | |
| segments = [entity for entity in intersection_result if isinstance(entity, Segment)] | |
| if points and segments: | |
| points_in_segments = list(uniq([point for point in points for segment in segments if point in segment])) | |
| if points_in_segments: | |
| for i in points_in_segments: | |
| points.remove(i) | |
| return list(ordered(segments + points)) | |
| else: | |
| return list(ordered(intersection_result)) | |
| def cut_section(self, line): | |
| """ | |
| Returns a tuple of two polygon segments that lie above and below | |
| the intersecting line respectively. | |
| Parameters | |
| ========== | |
| line: Line object of geometry module | |
| line which cuts the Polygon. The part of the Polygon that lies | |
| above and below this line is returned. | |
| Returns | |
| ======= | |
| upper_polygon, lower_polygon: Polygon objects or None | |
| upper_polygon is the polygon that lies above the given line. | |
| lower_polygon is the polygon that lies below the given line. | |
| upper_polygon and lower polygon are ``None`` when no polygon | |
| exists above the line or below the line. | |
| Raises | |
| ====== | |
| ValueError: When the line does not intersect the polygon | |
| Examples | |
| ======== | |
| >>> from sympy import Polygon, Line | |
| >>> a, b = 20, 10 | |
| >>> p1, p2, p3, p4 = [(0, b), (0, 0), (a, 0), (a, b)] | |
| >>> rectangle = Polygon(p1, p2, p3, p4) | |
| >>> t = rectangle.cut_section(Line((0, 5), slope=0)) | |
| >>> t | |
| (Polygon(Point2D(0, 10), Point2D(0, 5), Point2D(20, 5), Point2D(20, 10)), | |
| Polygon(Point2D(0, 5), Point2D(0, 0), Point2D(20, 0), Point2D(20, 5))) | |
| >>> upper_segment, lower_segment = t | |
| >>> upper_segment.area | |
| 100 | |
| >>> upper_segment.centroid | |
| Point2D(10, 15/2) | |
| >>> lower_segment.centroid | |
| Point2D(10, 5/2) | |
| References | |
| ========== | |
| .. [1] https://github.com/sympy/sympy/wiki/A-method-to-return-a-cut-section-of-any-polygon-geometry | |
| """ | |
| intersection_points = self.intersection(line) | |
| if not intersection_points: | |
| raise ValueError("This line does not intersect the polygon") | |
| points = list(self.vertices) | |
| points.append(points[0]) | |
| eq = line.equation(x, y) | |
| # considering equation of line to be `ax +by + c` | |
| a = eq.coeff(x) | |
| b = eq.coeff(y) | |
| upper_vertices = [] | |
| lower_vertices = [] | |
| # prev is true when previous point is above the line | |
| prev = True | |
| prev_point = None | |
| for point in points: | |
| # when coefficient of y is 0, right side of the line is | |
| # considered | |
| compare = eq.subs({x: point.x, y: point.y})/b if b \ | |
| else eq.subs(x, point.x)/a | |
| # if point lies above line | |
| if compare > 0: | |
| if not prev: | |
| # if previous point lies below the line, the intersection | |
| # point of the polygon edge and the line has to be included | |
| edge = Line(point, prev_point) | |
| new_point = edge.intersection(line) | |
| upper_vertices.append(new_point[0]) | |
| lower_vertices.append(new_point[0]) | |
| upper_vertices.append(point) | |
| prev = True | |
| else: | |
| if prev and prev_point: | |
| edge = Line(point, prev_point) | |
| new_point = edge.intersection(line) | |
| upper_vertices.append(new_point[0]) | |
| lower_vertices.append(new_point[0]) | |
| lower_vertices.append(point) | |
| prev = False | |
| prev_point = point | |
| upper_polygon, lower_polygon = None, None | |
| if upper_vertices and isinstance(Polygon(*upper_vertices), Polygon): | |
| upper_polygon = Polygon(*upper_vertices) | |
| if lower_vertices and isinstance(Polygon(*lower_vertices), Polygon): | |
| lower_polygon = Polygon(*lower_vertices) | |
| return upper_polygon, lower_polygon | |
| def distance(self, o): | |
| """ | |
| Returns the shortest distance between self and o. | |
| If o is a point, then self does not need to be convex. | |
| If o is another polygon self and o must be convex. | |
| Examples | |
| ======== | |
| >>> from sympy import Point, Polygon, RegularPolygon | |
| >>> p1, p2 = map(Point, [(0, 0), (7, 5)]) | |
| >>> poly = Polygon(*RegularPolygon(p1, 1, 3).vertices) | |
| >>> poly.distance(p2) | |
| sqrt(61) | |
| """ | |
| if isinstance(o, Point): | |
| dist = oo | |
| for side in self.sides: | |
| current = side.distance(o) | |
| if current == 0: | |
| return S.Zero | |
| elif current < dist: | |
| dist = current | |
| return dist | |
| elif isinstance(o, Polygon) and self.is_convex() and o.is_convex(): | |
| return self._do_poly_distance(o) | |
| raise NotImplementedError() | |
| def _do_poly_distance(self, e2): | |
| """ | |
| Calculates the least distance between the exteriors of two | |
| convex polygons e1 and e2. Does not check for the convexity | |
| of the polygons as this is checked by Polygon.distance. | |
| Notes | |
| ===== | |
| - Prints a warning if the two polygons possibly intersect as the return | |
| value will not be valid in such a case. For a more through test of | |
| intersection use intersection(). | |
| See Also | |
| ======== | |
| sympy.geometry.point.Point.distance | |
| Examples | |
| ======== | |
| >>> from sympy import Point, Polygon | |
| >>> square = Polygon(Point(0, 0), Point(0, 1), Point(1, 1), Point(1, 0)) | |
| >>> triangle = Polygon(Point(1, 2), Point(2, 2), Point(2, 1)) | |
| >>> square._do_poly_distance(triangle) | |
| sqrt(2)/2 | |
| Description of method used | |
| ========================== | |
| Method: | |
| [1] https://web.archive.org/web/20150509035744/http://cgm.cs.mcgill.ca/~orm/mind2p.html | |
| Uses rotating calipers: | |
| [2] https://en.wikipedia.org/wiki/Rotating_calipers | |
| and antipodal points: | |
| [3] https://en.wikipedia.org/wiki/Antipodal_point | |
| """ | |
| e1 = self | |
| '''Tests for a possible intersection between the polygons and outputs a warning''' | |
| e1_center = e1.centroid | |
| e2_center = e2.centroid | |
| e1_max_radius = S.Zero | |
| e2_max_radius = S.Zero | |
| for vertex in e1.vertices: | |
| r = Point.distance(e1_center, vertex) | |
| if e1_max_radius < r: | |
| e1_max_radius = r | |
| for vertex in e2.vertices: | |
| r = Point.distance(e2_center, vertex) | |
| if e2_max_radius < r: | |
| e2_max_radius = r | |
| center_dist = Point.distance(e1_center, e2_center) | |
| if center_dist <= e1_max_radius + e2_max_radius: | |
| warnings.warn("Polygons may intersect producing erroneous output", | |
| stacklevel=3) | |
| ''' | |
| Find the upper rightmost vertex of e1 and the lowest leftmost vertex of e2 | |
| ''' | |
| e1_ymax = Point(0, -oo) | |
| e2_ymin = Point(0, oo) | |
| for vertex in e1.vertices: | |
| if vertex.y > e1_ymax.y or (vertex.y == e1_ymax.y and vertex.x > e1_ymax.x): | |
| e1_ymax = vertex | |
| for vertex in e2.vertices: | |
| if vertex.y < e2_ymin.y or (vertex.y == e2_ymin.y and vertex.x < e2_ymin.x): | |
| e2_ymin = vertex | |
| min_dist = Point.distance(e1_ymax, e2_ymin) | |
| ''' | |
| Produce a dictionary with vertices of e1 as the keys and, for each vertex, the points | |
| to which the vertex is connected as its value. The same is then done for e2. | |
| ''' | |
| e1_connections = {} | |
| e2_connections = {} | |
| for side in e1.sides: | |
| if side.p1 in e1_connections: | |
| e1_connections[side.p1].append(side.p2) | |
| else: | |
| e1_connections[side.p1] = [side.p2] | |
| if side.p2 in e1_connections: | |
| e1_connections[side.p2].append(side.p1) | |
| else: | |
| e1_connections[side.p2] = [side.p1] | |
| for side in e2.sides: | |
| if side.p1 in e2_connections: | |
| e2_connections[side.p1].append(side.p2) | |
| else: | |
| e2_connections[side.p1] = [side.p2] | |
| if side.p2 in e2_connections: | |
| e2_connections[side.p2].append(side.p1) | |
| else: | |
| e2_connections[side.p2] = [side.p1] | |
| e1_current = e1_ymax | |
| e2_current = e2_ymin | |
| support_line = Line(Point(S.Zero, S.Zero), Point(S.One, S.Zero)) | |
| ''' | |
| Determine which point in e1 and e2 will be selected after e2_ymin and e1_ymax, | |
| this information combined with the above produced dictionaries determines the | |
| path that will be taken around the polygons | |
| ''' | |
| point1 = e1_connections[e1_ymax][0] | |
| point2 = e1_connections[e1_ymax][1] | |
| angle1 = support_line.angle_between(Line(e1_ymax, point1)) | |
| angle2 = support_line.angle_between(Line(e1_ymax, point2)) | |
| if angle1 < angle2: | |
| e1_next = point1 | |
| elif angle2 < angle1: | |
| e1_next = point2 | |
| elif Point.distance(e1_ymax, point1) > Point.distance(e1_ymax, point2): | |
| e1_next = point2 | |
| else: | |
| e1_next = point1 | |
| point1 = e2_connections[e2_ymin][0] | |
| point2 = e2_connections[e2_ymin][1] | |
| angle1 = support_line.angle_between(Line(e2_ymin, point1)) | |
| angle2 = support_line.angle_between(Line(e2_ymin, point2)) | |
| if angle1 > angle2: | |
| e2_next = point1 | |
| elif angle2 > angle1: | |
| e2_next = point2 | |
| elif Point.distance(e2_ymin, point1) > Point.distance(e2_ymin, point2): | |
| e2_next = point2 | |
| else: | |
| e2_next = point1 | |
| ''' | |
| Loop which determines the distance between anti-podal pairs and updates the | |
| minimum distance accordingly. It repeats until it reaches the starting position. | |
| ''' | |
| while True: | |
| e1_angle = support_line.angle_between(Line(e1_current, e1_next)) | |
| e2_angle = pi - support_line.angle_between(Line( | |
| e2_current, e2_next)) | |
| if (e1_angle < e2_angle) is True: | |
| support_line = Line(e1_current, e1_next) | |
| e1_segment = Segment(e1_current, e1_next) | |
| min_dist_current = e1_segment.distance(e2_current) | |
| if min_dist_current.evalf() < min_dist.evalf(): | |
| min_dist = min_dist_current | |
| if e1_connections[e1_next][0] != e1_current: | |
| e1_current = e1_next | |
| e1_next = e1_connections[e1_next][0] | |
| else: | |
| e1_current = e1_next | |
| e1_next = e1_connections[e1_next][1] | |
| elif (e1_angle > e2_angle) is True: | |
| support_line = Line(e2_next, e2_current) | |
| e2_segment = Segment(e2_current, e2_next) | |
| min_dist_current = e2_segment.distance(e1_current) | |
| if min_dist_current.evalf() < min_dist.evalf(): | |
| min_dist = min_dist_current | |
| if e2_connections[e2_next][0] != e2_current: | |
| e2_current = e2_next | |
| e2_next = e2_connections[e2_next][0] | |
| else: | |
| e2_current = e2_next | |
| e2_next = e2_connections[e2_next][1] | |
| else: | |
| support_line = Line(e1_current, e1_next) | |
| e1_segment = Segment(e1_current, e1_next) | |
| e2_segment = Segment(e2_current, e2_next) | |
| min1 = e1_segment.distance(e2_next) | |
| min2 = e2_segment.distance(e1_next) | |
| min_dist_current = min(min1, min2) | |
| if min_dist_current.evalf() < min_dist.evalf(): | |
| min_dist = min_dist_current | |
| if e1_connections[e1_next][0] != e1_current: | |
| e1_current = e1_next | |
| e1_next = e1_connections[e1_next][0] | |
| else: | |
| e1_current = e1_next | |
| e1_next = e1_connections[e1_next][1] | |
| if e2_connections[e2_next][0] != e2_current: | |
| e2_current = e2_next | |
| e2_next = e2_connections[e2_next][0] | |
| else: | |
| e2_current = e2_next | |
| e2_next = e2_connections[e2_next][1] | |
| if e1_current == e1_ymax and e2_current == e2_ymin: | |
| break | |
| return min_dist | |
| def _svg(self, scale_factor=1., fill_color="#66cc99"): | |
| """Returns SVG path element for the Polygon. | |
| Parameters | |
| ========== | |
| scale_factor : float | |
| Multiplication factor for the SVG stroke-width. Default is 1. | |
| fill_color : str, optional | |
| Hex string for fill color. Default is "#66cc99". | |
| """ | |
| verts = map(N, self.vertices) | |
| coords = ["{},{}".format(p.x, p.y) for p in verts] | |
| path = "M {} L {} z".format(coords[0], " L ".join(coords[1:])) | |
| return ( | |
| '<path fill-rule="evenodd" fill="{2}" stroke="#555555" ' | |
| 'stroke-width="{0}" opacity="0.6" d="{1}" />' | |
| ).format(2. * scale_factor, path, fill_color) | |
| def _hashable_content(self): | |
| D = {} | |
| def ref_list(point_list): | |
| kee = {} | |
| for i, p in enumerate(ordered(set(point_list))): | |
| kee[p] = i | |
| D[i] = p | |
| return [kee[p] for p in point_list] | |
| S1 = ref_list(self.args) | |
| r_nor = rotate_left(S1, least_rotation(S1)) | |
| S2 = ref_list(list(reversed(self.args))) | |
| r_rev = rotate_left(S2, least_rotation(S2)) | |
| if r_nor < r_rev: | |
| r = r_nor | |
| else: | |
| r = r_rev | |
| canonical_args = [ D[order] for order in r ] | |
| return tuple(canonical_args) | |
| def __contains__(self, o): | |
| """ | |
| Return True if o is contained within the boundary lines of self.altitudes | |
| Parameters | |
| ========== | |
| other : GeometryEntity | |
| Returns | |
| ======= | |
| contained in : bool | |
| The points (and sides, if applicable) are contained in self. | |
| See Also | |
| ======== | |
| sympy.geometry.entity.GeometryEntity.encloses | |
| Examples | |
| ======== | |
| >>> from sympy import Line, Segment, Point | |
| >>> p = Point(0, 0) | |
| >>> q = Point(1, 1) | |
| >>> s = Segment(p, q*2) | |
| >>> l = Line(p, q) | |
| >>> p in q | |
| False | |
| >>> p in s | |
| True | |
| >>> q*3 in s | |
| False | |
| >>> s in l | |
| True | |
| """ | |
| if isinstance(o, Polygon): | |
| return self == o | |
| elif isinstance(o, Segment): | |
| return any(o in s for s in self.sides) | |
| elif isinstance(o, Point): | |
| if o in self.vertices: | |
| return True | |
| for side in self.sides: | |
| if o in side: | |
| return True | |
| return False | |
| def bisectors(p, prec=None): | |
| """Returns angle bisectors of a polygon. If prec is given | |
| then approximate the point defining the ray to that precision. | |
| The distance between the points defining the bisector ray is 1. | |
| Examples | |
| ======== | |
| >>> from sympy import Polygon, Point | |
| >>> p = Polygon(Point(0, 0), Point(2, 0), Point(1, 1), Point(0, 3)) | |
| >>> p.bisectors(2) | |
| {Point2D(0, 0): Ray2D(Point2D(0, 0), Point2D(0.71, 0.71)), | |
| Point2D(0, 3): Ray2D(Point2D(0, 3), Point2D(0.23, 2.0)), | |
| Point2D(1, 1): Ray2D(Point2D(1, 1), Point2D(0.19, 0.42)), | |
| Point2D(2, 0): Ray2D(Point2D(2, 0), Point2D(1.1, 0.38))} | |
| """ | |
| b = {} | |
| pts = list(p.args) | |
| pts.append(pts[0]) # close it | |
| cw = Polygon._is_clockwise(*pts[:3]) | |
| if cw: | |
| pts = list(reversed(pts)) | |
| for v, a in p.angles.items(): | |
| i = pts.index(v) | |
| p1, p2 = Point._normalize_dimension(pts[i], pts[i + 1]) | |
| ray = Ray(p1, p2).rotate(a/2, v) | |
| dir = ray.direction | |
| ray = Ray(ray.p1, ray.p1 + dir/dir.distance((0, 0))) | |
| if prec is not None: | |
| ray = Ray(ray.p1, ray.p2.n(prec)) | |
| b[v] = ray | |
| return b | |
| class RegularPolygon(Polygon): | |
| """ | |
| A regular polygon. | |
| Such a polygon has all internal angles equal and all sides the same length. | |
| Parameters | |
| ========== | |
| center : Point | |
| radius : number or Basic instance | |
| The distance from the center to a vertex | |
| n : int | |
| The number of sides | |
| Attributes | |
| ========== | |
| vertices | |
| center | |
| radius | |
| rotation | |
| apothem | |
| interior_angle | |
| exterior_angle | |
| circumcircle | |
| incircle | |
| angles | |
| Raises | |
| ====== | |
| GeometryError | |
| If the `center` is not a Point, or the `radius` is not a number or Basic | |
| instance, or the number of sides, `n`, is less than three. | |
| Notes | |
| ===== | |
| A RegularPolygon can be instantiated with Polygon with the kwarg n. | |
| Regular polygons are instantiated with a center, radius, number of sides | |
| and a rotation angle. Whereas the arguments of a Polygon are vertices, the | |
| vertices of the RegularPolygon must be obtained with the vertices method. | |
| See Also | |
| ======== | |
| sympy.geometry.point.Point, Polygon | |
| Examples | |
| ======== | |
| >>> from sympy import RegularPolygon, Point | |
| >>> r = RegularPolygon(Point(0, 0), 5, 3) | |
| >>> r | |
| RegularPolygon(Point2D(0, 0), 5, 3, 0) | |
| >>> r.vertices[0] | |
| Point2D(5, 0) | |
| """ | |
| __slots__ = ('_n', '_center', '_radius', '_rot') | |
| def __new__(self, c, r, n, rot=0, **kwargs): | |
| r, n, rot = map(sympify, (r, n, rot)) | |
| c = Point(c, dim=2, **kwargs) | |
| if not isinstance(r, Expr): | |
| raise GeometryError("r must be an Expr object, not %s" % r) | |
| if n.is_Number: | |
| as_int(n) # let an error raise if necessary | |
| if n < 3: | |
| raise GeometryError("n must be a >= 3, not %s" % n) | |
| obj = GeometryEntity.__new__(self, c, r, n, **kwargs) | |
| obj._n = n | |
| obj._center = c | |
| obj._radius = r | |
| obj._rot = rot % (2*S.Pi/n) if rot.is_number else rot | |
| return obj | |
| def _eval_evalf(self, prec=15, **options): | |
| c, r, n, a = self.args | |
| dps = prec_to_dps(prec) | |
| c, r, a = [i.evalf(n=dps, **options) for i in (c, r, a)] | |
| return self.func(c, r, n, a) | |
| def args(self): | |
| """ | |
| Returns the center point, the radius, | |
| the number of sides, and the orientation angle. | |
| Examples | |
| ======== | |
| >>> from sympy import RegularPolygon, Point | |
| >>> r = RegularPolygon(Point(0, 0), 5, 3) | |
| >>> r.args | |
| (Point2D(0, 0), 5, 3, 0) | |
| """ | |
| return self._center, self._radius, self._n, self._rot | |
| def __str__(self): | |
| return 'RegularPolygon(%s, %s, %s, %s)' % tuple(self.args) | |
| def __repr__(self): | |
| return 'RegularPolygon(%s, %s, %s, %s)' % tuple(self.args) | |
| def area(self): | |
| """Returns the area. | |
| Examples | |
| ======== | |
| >>> from sympy import RegularPolygon | |
| >>> square = RegularPolygon((0, 0), 1, 4) | |
| >>> square.area | |
| 2 | |
| >>> _ == square.length**2 | |
| True | |
| """ | |
| c, r, n, rot = self.args | |
| return sign(r)*n*self.length**2/(4*tan(pi/n)) | |
| def length(self): | |
| """Returns the length of the sides. | |
| The half-length of the side and the apothem form two legs | |
| of a right triangle whose hypotenuse is the radius of the | |
| regular polygon. | |
| Examples | |
| ======== | |
| >>> from sympy import RegularPolygon | |
| >>> from sympy import sqrt | |
| >>> s = square_in_unit_circle = RegularPolygon((0, 0), 1, 4) | |
| >>> s.length | |
| sqrt(2) | |
| >>> sqrt((_/2)**2 + s.apothem**2) == s.radius | |
| True | |
| """ | |
| return self.radius*2*sin(pi/self._n) | |
| def center(self): | |
| """The center of the RegularPolygon | |
| This is also the center of the circumscribing circle. | |
| Returns | |
| ======= | |
| center : Point | |
| See Also | |
| ======== | |
| sympy.geometry.point.Point, sympy.geometry.ellipse.Ellipse.center | |
| Examples | |
| ======== | |
| >>> from sympy import RegularPolygon, Point | |
| >>> rp = RegularPolygon(Point(0, 0), 5, 4) | |
| >>> rp.center | |
| Point2D(0, 0) | |
| """ | |
| return self._center | |
| centroid = center | |
| def circumcenter(self): | |
| """ | |
| Alias for center. | |
| Examples | |
| ======== | |
| >>> from sympy import RegularPolygon, Point | |
| >>> rp = RegularPolygon(Point(0, 0), 5, 4) | |
| >>> rp.circumcenter | |
| Point2D(0, 0) | |
| """ | |
| return self.center | |
| def radius(self): | |
| """Radius of the RegularPolygon | |
| This is also the radius of the circumscribing circle. | |
| Returns | |
| ======= | |
| radius : number or instance of Basic | |
| See Also | |
| ======== | |
| sympy.geometry.line.Segment.length, sympy.geometry.ellipse.Circle.radius | |
| Examples | |
| ======== | |
| >>> from sympy import Symbol | |
| >>> from sympy import RegularPolygon, Point | |
| >>> radius = Symbol('r') | |
| >>> rp = RegularPolygon(Point(0, 0), radius, 4) | |
| >>> rp.radius | |
| r | |
| """ | |
| return self._radius | |
| def circumradius(self): | |
| """ | |
| Alias for radius. | |
| Examples | |
| ======== | |
| >>> from sympy import Symbol | |
| >>> from sympy import RegularPolygon, Point | |
| >>> radius = Symbol('r') | |
| >>> rp = RegularPolygon(Point(0, 0), radius, 4) | |
| >>> rp.circumradius | |
| r | |
| """ | |
| return self.radius | |
| def rotation(self): | |
| """CCW angle by which the RegularPolygon is rotated | |
| Returns | |
| ======= | |
| rotation : number or instance of Basic | |
| Examples | |
| ======== | |
| >>> from sympy import pi | |
| >>> from sympy.abc import a | |
| >>> from sympy import RegularPolygon, Point | |
| >>> RegularPolygon(Point(0, 0), 3, 4, pi/4).rotation | |
| pi/4 | |
| Numerical rotation angles are made canonical: | |
| >>> RegularPolygon(Point(0, 0), 3, 4, a).rotation | |
| a | |
| >>> RegularPolygon(Point(0, 0), 3, 4, pi).rotation | |
| 0 | |
| """ | |
| return self._rot | |
| def apothem(self): | |
| """The inradius of the RegularPolygon. | |
| The apothem/inradius is the radius of the inscribed circle. | |
| Returns | |
| ======= | |
| apothem : number or instance of Basic | |
| See Also | |
| ======== | |
| sympy.geometry.line.Segment.length, sympy.geometry.ellipse.Circle.radius | |
| Examples | |
| ======== | |
| >>> from sympy import Symbol | |
| >>> from sympy import RegularPolygon, Point | |
| >>> radius = Symbol('r') | |
| >>> rp = RegularPolygon(Point(0, 0), radius, 4) | |
| >>> rp.apothem | |
| sqrt(2)*r/2 | |
| """ | |
| return self.radius * cos(S.Pi/self._n) | |
| def inradius(self): | |
| """ | |
| Alias for apothem. | |
| Examples | |
| ======== | |
| >>> from sympy import Symbol | |
| >>> from sympy import RegularPolygon, Point | |
| >>> radius = Symbol('r') | |
| >>> rp = RegularPolygon(Point(0, 0), radius, 4) | |
| >>> rp.inradius | |
| sqrt(2)*r/2 | |
| """ | |
| return self.apothem | |
| def interior_angle(self): | |
| """Measure of the interior angles. | |
| Returns | |
| ======= | |
| interior_angle : number | |
| See Also | |
| ======== | |
| sympy.geometry.line.LinearEntity.angle_between | |
| Examples | |
| ======== | |
| >>> from sympy import RegularPolygon, Point | |
| >>> rp = RegularPolygon(Point(0, 0), 4, 8) | |
| >>> rp.interior_angle | |
| 3*pi/4 | |
| """ | |
| return (self._n - 2)*S.Pi/self._n | |
| def exterior_angle(self): | |
| """Measure of the exterior angles. | |
| Returns | |
| ======= | |
| exterior_angle : number | |
| See Also | |
| ======== | |
| sympy.geometry.line.LinearEntity.angle_between | |
| Examples | |
| ======== | |
| >>> from sympy import RegularPolygon, Point | |
| >>> rp = RegularPolygon(Point(0, 0), 4, 8) | |
| >>> rp.exterior_angle | |
| pi/4 | |
| """ | |
| return 2*S.Pi/self._n | |
| def circumcircle(self): | |
| """The circumcircle of the RegularPolygon. | |
| Returns | |
| ======= | |
| circumcircle : Circle | |
| See Also | |
| ======== | |
| circumcenter, sympy.geometry.ellipse.Circle | |
| Examples | |
| ======== | |
| >>> from sympy import RegularPolygon, Point | |
| >>> rp = RegularPolygon(Point(0, 0), 4, 8) | |
| >>> rp.circumcircle | |
| Circle(Point2D(0, 0), 4) | |
| """ | |
| return Circle(self.center, self.radius) | |
| def incircle(self): | |
| """The incircle of the RegularPolygon. | |
| Returns | |
| ======= | |
| incircle : Circle | |
| See Also | |
| ======== | |
| inradius, sympy.geometry.ellipse.Circle | |
| Examples | |
| ======== | |
| >>> from sympy import RegularPolygon, Point | |
| >>> rp = RegularPolygon(Point(0, 0), 4, 7) | |
| >>> rp.incircle | |
| Circle(Point2D(0, 0), 4*cos(pi/7)) | |
| """ | |
| return Circle(self.center, self.apothem) | |
| def angles(self): | |
| """ | |
| Returns a dictionary with keys, the vertices of the Polygon, | |
| and values, the interior angle at each vertex. | |
| Examples | |
| ======== | |
| >>> from sympy import RegularPolygon, Point | |
| >>> r = RegularPolygon(Point(0, 0), 5, 3) | |
| >>> r.angles | |
| {Point2D(-5/2, -5*sqrt(3)/2): pi/3, | |
| Point2D(-5/2, 5*sqrt(3)/2): pi/3, | |
| Point2D(5, 0): pi/3} | |
| """ | |
| ret = {} | |
| ang = self.interior_angle | |
| for v in self.vertices: | |
| ret[v] = ang | |
| return ret | |
| def encloses_point(self, p): | |
| """ | |
| Return True if p is enclosed by (is inside of) self. | |
| Notes | |
| ===== | |
| Being on the border of self is considered False. | |
| The general Polygon.encloses_point method is called only if | |
| a point is not within or beyond the incircle or circumcircle, | |
| respectively. | |
| Parameters | |
| ========== | |
| p : Point | |
| Returns | |
| ======= | |
| encloses_point : True, False or None | |
| See Also | |
| ======== | |
| sympy.geometry.ellipse.Ellipse.encloses_point | |
| Examples | |
| ======== | |
| >>> from sympy import RegularPolygon, S, Point, Symbol | |
| >>> p = RegularPolygon((0, 0), 3, 4) | |
| >>> p.encloses_point(Point(0, 0)) | |
| True | |
| >>> r, R = p.inradius, p.circumradius | |
| >>> p.encloses_point(Point((r + R)/2, 0)) | |
| True | |
| >>> p.encloses_point(Point(R/2, R/2 + (R - r)/10)) | |
| False | |
| >>> t = Symbol('t', real=True) | |
| >>> p.encloses_point(p.arbitrary_point().subs(t, S.Half)) | |
| False | |
| >>> p.encloses_point(Point(5, 5)) | |
| False | |
| """ | |
| c = self.center | |
| d = Segment(c, p).length | |
| if d >= self.radius: | |
| return False | |
| elif d < self.inradius: | |
| return True | |
| else: | |
| # now enumerate the RegularPolygon like a general polygon. | |
| return Polygon.encloses_point(self, p) | |
| def spin(self, angle): | |
| """Increment *in place* the virtual Polygon's rotation by ccw angle. | |
| See also: rotate method which moves the center. | |
| >>> from sympy import Polygon, Point, pi | |
| >>> r = Polygon(Point(0,0), 1, n=3) | |
| >>> r.vertices[0] | |
| Point2D(1, 0) | |
| >>> r.spin(pi/6) | |
| >>> r.vertices[0] | |
| Point2D(sqrt(3)/2, 1/2) | |
| See Also | |
| ======== | |
| rotation | |
| rotate : Creates a copy of the RegularPolygon rotated about a Point | |
| """ | |
| self._rot += angle | |
| def rotate(self, angle, pt=None): | |
| """Override GeometryEntity.rotate to first rotate the RegularPolygon | |
| about its center. | |
| >>> from sympy import Point, RegularPolygon, pi | |
| >>> t = RegularPolygon(Point(1, 0), 1, 3) | |
| >>> t.vertices[0] # vertex on x-axis | |
| Point2D(2, 0) | |
| >>> t.rotate(pi/2).vertices[0] # vertex on y axis now | |
| Point2D(0, 2) | |
| See Also | |
| ======== | |
| rotation | |
| spin : Rotates a RegularPolygon in place | |
| """ | |
| r = type(self)(*self.args) # need a copy or else changes are in-place | |
| r._rot += angle | |
| return GeometryEntity.rotate(r, angle, pt) | |
| def scale(self, x=1, y=1, pt=None): | |
| """Override GeometryEntity.scale since it is the radius that must be | |
| scaled (if x == y) or else a new Polygon must be returned. | |
| >>> from sympy import RegularPolygon | |
| Symmetric scaling returns a RegularPolygon: | |
| >>> RegularPolygon((0, 0), 1, 4).scale(2, 2) | |
| RegularPolygon(Point2D(0, 0), 2, 4, 0) | |
| Asymmetric scaling returns a kite as a Polygon: | |
| >>> RegularPolygon((0, 0), 1, 4).scale(2, 1) | |
| Polygon(Point2D(2, 0), Point2D(0, 1), Point2D(-2, 0), Point2D(0, -1)) | |
| """ | |
| if pt: | |
| pt = Point(pt, dim=2) | |
| return self.translate(*(-pt).args).scale(x, y).translate(*pt.args) | |
| if x != y: | |
| return Polygon(*self.vertices).scale(x, y) | |
| c, r, n, rot = self.args | |
| r *= x | |
| return self.func(c, r, n, rot) | |
| def reflect(self, line): | |
| """Override GeometryEntity.reflect since this is not made of only | |
| points. | |
| Examples | |
| ======== | |
| >>> from sympy import RegularPolygon, Line | |
| >>> RegularPolygon((0, 0), 1, 4).reflect(Line((0, 1), slope=-2)) | |
| RegularPolygon(Point2D(4/5, 2/5), -1, 4, atan(4/3)) | |
| """ | |
| c, r, n, rot = self.args | |
| v = self.vertices[0] | |
| d = v - c | |
| cc = c.reflect(line) | |
| vv = v.reflect(line) | |
| dd = vv - cc | |
| # calculate rotation about the new center | |
| # which will align the vertices | |
| l1 = Ray((0, 0), dd) | |
| l2 = Ray((0, 0), d) | |
| ang = l1.closing_angle(l2) | |
| rot += ang | |
| # change sign of radius as point traversal is reversed | |
| return self.func(cc, -r, n, rot) | |
| def vertices(self): | |
| """The vertices of the RegularPolygon. | |
| Returns | |
| ======= | |
| vertices : list | |
| Each vertex is a Point. | |
| See Also | |
| ======== | |
| sympy.geometry.point.Point | |
| Examples | |
| ======== | |
| >>> from sympy import RegularPolygon, Point | |
| >>> rp = RegularPolygon(Point(0, 0), 5, 4) | |
| >>> rp.vertices | |
| [Point2D(5, 0), Point2D(0, 5), Point2D(-5, 0), Point2D(0, -5)] | |
| """ | |
| c = self._center | |
| r = abs(self._radius) | |
| rot = self._rot | |
| v = 2*S.Pi/self._n | |
| return [Point(c.x + r*cos(k*v + rot), c.y + r*sin(k*v + rot)) | |
| for k in range(self._n)] | |
| def __eq__(self, o): | |
| if not isinstance(o, Polygon): | |
| return False | |
| elif not isinstance(o, RegularPolygon): | |
| return Polygon.__eq__(o, self) | |
| return self.args == o.args | |
| def __hash__(self): | |
| return super().__hash__() | |
| class Triangle(Polygon): | |
| """ | |
| A polygon with three vertices and three sides. | |
| Parameters | |
| ========== | |
| points : sequence of Points | |
| keyword: asa, sas, or sss to specify sides/angles of the triangle | |
| Attributes | |
| ========== | |
| vertices | |
| altitudes | |
| orthocenter | |
| circumcenter | |
| circumradius | |
| circumcircle | |
| inradius | |
| incircle | |
| exradii | |
| medians | |
| medial | |
| nine_point_circle | |
| Raises | |
| ====== | |
| GeometryError | |
| If the number of vertices is not equal to three, or one of the vertices | |
| is not a Point, or a valid keyword is not given. | |
| See Also | |
| ======== | |
| sympy.geometry.point.Point, Polygon | |
| Examples | |
| ======== | |
| >>> from sympy import Triangle, Point | |
| >>> Triangle(Point(0, 0), Point(4, 0), Point(4, 3)) | |
| Triangle(Point2D(0, 0), Point2D(4, 0), Point2D(4, 3)) | |
| Keywords sss, sas, or asa can be used to give the desired | |
| side lengths (in order) and interior angles (in degrees) that | |
| define the triangle: | |
| >>> Triangle(sss=(3, 4, 5)) | |
| Triangle(Point2D(0, 0), Point2D(3, 0), Point2D(3, 4)) | |
| >>> Triangle(asa=(30, 1, 30)) | |
| Triangle(Point2D(0, 0), Point2D(1, 0), Point2D(1/2, sqrt(3)/6)) | |
| >>> Triangle(sas=(1, 45, 2)) | |
| Triangle(Point2D(0, 0), Point2D(2, 0), Point2D(sqrt(2)/2, sqrt(2)/2)) | |
| """ | |
| def __new__(cls, *args, **kwargs): | |
| if len(args) != 3: | |
| if 'sss' in kwargs: | |
| return _sss(*[simplify(a) for a in kwargs['sss']]) | |
| if 'asa' in kwargs: | |
| return _asa(*[simplify(a) for a in kwargs['asa']]) | |
| if 'sas' in kwargs: | |
| return _sas(*[simplify(a) for a in kwargs['sas']]) | |
| msg = "Triangle instantiates with three points or a valid keyword." | |
| raise GeometryError(msg) | |
| vertices = [Point(a, dim=2, **kwargs) for a in args] | |
| # remove consecutive duplicates | |
| nodup = [] | |
| for p in vertices: | |
| if nodup and p == nodup[-1]: | |
| continue | |
| nodup.append(p) | |
| if len(nodup) > 1 and nodup[-1] == nodup[0]: | |
| nodup.pop() # last point was same as first | |
| # remove collinear points | |
| i = -3 | |
| while i < len(nodup) - 3 and len(nodup) > 2: | |
| a, b, c = sorted( | |
| [nodup[i], nodup[i + 1], nodup[i + 2]], key=default_sort_key) | |
| if Point.is_collinear(a, b, c): | |
| nodup[i] = a | |
| nodup[i + 1] = None | |
| nodup.pop(i + 1) | |
| i += 1 | |
| vertices = list(filter(lambda x: x is not None, nodup)) | |
| if len(vertices) == 3: | |
| return GeometryEntity.__new__(cls, *vertices, **kwargs) | |
| elif len(vertices) == 2: | |
| return Segment(*vertices, **kwargs) | |
| else: | |
| return Point(*vertices, **kwargs) | |
| def vertices(self): | |
| """The triangle's vertices | |
| Returns | |
| ======= | |
| vertices : tuple | |
| Each element in the tuple is a Point | |
| See Also | |
| ======== | |
| sympy.geometry.point.Point | |
| Examples | |
| ======== | |
| >>> from sympy import Triangle, Point | |
| >>> t = Triangle(Point(0, 0), Point(4, 0), Point(4, 3)) | |
| >>> t.vertices | |
| (Point2D(0, 0), Point2D(4, 0), Point2D(4, 3)) | |
| """ | |
| return self.args | |
| def is_similar(t1, t2): | |
| """Is another triangle similar to this one. | |
| Two triangles are similar if one can be uniformly scaled to the other. | |
| Parameters | |
| ========== | |
| other: Triangle | |
| Returns | |
| ======= | |
| is_similar : boolean | |
| See Also | |
| ======== | |
| sympy.geometry.entity.GeometryEntity.is_similar | |
| Examples | |
| ======== | |
| >>> from sympy import Triangle, Point | |
| >>> t1 = Triangle(Point(0, 0), Point(4, 0), Point(4, 3)) | |
| >>> t2 = Triangle(Point(0, 0), Point(-4, 0), Point(-4, -3)) | |
| >>> t1.is_similar(t2) | |
| True | |
| >>> t2 = Triangle(Point(0, 0), Point(-4, 0), Point(-4, -4)) | |
| >>> t1.is_similar(t2) | |
| False | |
| """ | |
| if not isinstance(t2, Polygon): | |
| return False | |
| s1_1, s1_2, s1_3 = [side.length for side in t1.sides] | |
| s2 = [side.length for side in t2.sides] | |
| def _are_similar(u1, u2, u3, v1, v2, v3): | |
| e1 = simplify(u1/v1) | |
| e2 = simplify(u2/v2) | |
| e3 = simplify(u3/v3) | |
| return bool(e1 == e2) and bool(e2 == e3) | |
| # There's only 6 permutations, so write them out | |
| return _are_similar(s1_1, s1_2, s1_3, *s2) or \ | |
| _are_similar(s1_1, s1_3, s1_2, *s2) or \ | |
| _are_similar(s1_2, s1_1, s1_3, *s2) or \ | |
| _are_similar(s1_2, s1_3, s1_1, *s2) or \ | |
| _are_similar(s1_3, s1_1, s1_2, *s2) or \ | |
| _are_similar(s1_3, s1_2, s1_1, *s2) | |
| def is_equilateral(self): | |
| """Are all the sides the same length? | |
| Returns | |
| ======= | |
| is_equilateral : boolean | |
| See Also | |
| ======== | |
| sympy.geometry.entity.GeometryEntity.is_similar, RegularPolygon | |
| is_isosceles, is_right, is_scalene | |
| Examples | |
| ======== | |
| >>> from sympy import Triangle, Point | |
| >>> t1 = Triangle(Point(0, 0), Point(4, 0), Point(4, 3)) | |
| >>> t1.is_equilateral() | |
| False | |
| >>> from sympy import sqrt | |
| >>> t2 = Triangle(Point(0, 0), Point(10, 0), Point(5, 5*sqrt(3))) | |
| >>> t2.is_equilateral() | |
| True | |
| """ | |
| return not has_variety(s.length for s in self.sides) | |
| def is_isosceles(self): | |
| """Are two or more of the sides the same length? | |
| Returns | |
| ======= | |
| is_isosceles : boolean | |
| See Also | |
| ======== | |
| is_equilateral, is_right, is_scalene | |
| Examples | |
| ======== | |
| >>> from sympy import Triangle, Point | |
| >>> t1 = Triangle(Point(0, 0), Point(4, 0), Point(2, 4)) | |
| >>> t1.is_isosceles() | |
| True | |
| """ | |
| return has_dups(s.length for s in self.sides) | |
| def is_scalene(self): | |
| """Are all the sides of the triangle of different lengths? | |
| Returns | |
| ======= | |
| is_scalene : boolean | |
| See Also | |
| ======== | |
| is_equilateral, is_isosceles, is_right | |
| Examples | |
| ======== | |
| >>> from sympy import Triangle, Point | |
| >>> t1 = Triangle(Point(0, 0), Point(4, 0), Point(1, 4)) | |
| >>> t1.is_scalene() | |
| True | |
| """ | |
| return not has_dups(s.length for s in self.sides) | |
| def is_right(self): | |
| """Is the triangle right-angled. | |
| Returns | |
| ======= | |
| is_right : boolean | |
| See Also | |
| ======== | |
| sympy.geometry.line.LinearEntity.is_perpendicular | |
| is_equilateral, is_isosceles, is_scalene | |
| Examples | |
| ======== | |
| >>> from sympy import Triangle, Point | |
| >>> t1 = Triangle(Point(0, 0), Point(4, 0), Point(4, 3)) | |
| >>> t1.is_right() | |
| True | |
| """ | |
| s = self.sides | |
| return Segment.is_perpendicular(s[0], s[1]) or \ | |
| Segment.is_perpendicular(s[1], s[2]) or \ | |
| Segment.is_perpendicular(s[0], s[2]) | |
| def altitudes(self): | |
| """The altitudes of the triangle. | |
| An altitude of a triangle is a segment through a vertex, | |
| perpendicular to the opposite side, with length being the | |
| height of the vertex measured from the line containing the side. | |
| Returns | |
| ======= | |
| altitudes : dict | |
| The dictionary consists of keys which are vertices and values | |
| which are Segments. | |
| See Also | |
| ======== | |
| sympy.geometry.point.Point, sympy.geometry.line.Segment.length | |
| Examples | |
| ======== | |
| >>> from sympy import Point, Triangle | |
| >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1) | |
| >>> t = Triangle(p1, p2, p3) | |
| >>> t.altitudes[p1] | |
| Segment2D(Point2D(0, 0), Point2D(1/2, 1/2)) | |
| """ | |
| s = self.sides | |
| v = self.vertices | |
| return {v[0]: s[1].perpendicular_segment(v[0]), | |
| v[1]: s[2].perpendicular_segment(v[1]), | |
| v[2]: s[0].perpendicular_segment(v[2])} | |
| def orthocenter(self): | |
| """The orthocenter of the triangle. | |
| The orthocenter is the intersection of the altitudes of a triangle. | |
| It may lie inside, outside or on the triangle. | |
| Returns | |
| ======= | |
| orthocenter : Point | |
| See Also | |
| ======== | |
| sympy.geometry.point.Point | |
| Examples | |
| ======== | |
| >>> from sympy import Point, Triangle | |
| >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1) | |
| >>> t = Triangle(p1, p2, p3) | |
| >>> t.orthocenter | |
| Point2D(0, 0) | |
| """ | |
| a = self.altitudes | |
| v = self.vertices | |
| return Line(a[v[0]]).intersection(Line(a[v[1]]))[0] | |
| def circumcenter(self): | |
| """The circumcenter of the triangle | |
| The circumcenter is the center of the circumcircle. | |
| Returns | |
| ======= | |
| circumcenter : Point | |
| See Also | |
| ======== | |
| sympy.geometry.point.Point | |
| Examples | |
| ======== | |
| >>> from sympy import Point, Triangle | |
| >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1) | |
| >>> t = Triangle(p1, p2, p3) | |
| >>> t.circumcenter | |
| Point2D(1/2, 1/2) | |
| """ | |
| a, b, c = [x.perpendicular_bisector() for x in self.sides] | |
| return a.intersection(b)[0] | |
| def circumradius(self): | |
| """The radius of the circumcircle of the triangle. | |
| Returns | |
| ======= | |
| circumradius : number of Basic instance | |
| See Also | |
| ======== | |
| sympy.geometry.ellipse.Circle.radius | |
| Examples | |
| ======== | |
| >>> from sympy import Symbol | |
| >>> from sympy import Point, Triangle | |
| >>> a = Symbol('a') | |
| >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, a) | |
| >>> t = Triangle(p1, p2, p3) | |
| >>> t.circumradius | |
| sqrt(a**2/4 + 1/4) | |
| """ | |
| return Point.distance(self.circumcenter, self.vertices[0]) | |
| def circumcircle(self): | |
| """The circle which passes through the three vertices of the triangle. | |
| Returns | |
| ======= | |
| circumcircle : Circle | |
| See Also | |
| ======== | |
| sympy.geometry.ellipse.Circle | |
| Examples | |
| ======== | |
| >>> from sympy import Point, Triangle | |
| >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1) | |
| >>> t = Triangle(p1, p2, p3) | |
| >>> t.circumcircle | |
| Circle(Point2D(1/2, 1/2), sqrt(2)/2) | |
| """ | |
| return Circle(self.circumcenter, self.circumradius) | |
| def bisectors(self): | |
| """The angle bisectors of the triangle. | |
| An angle bisector of a triangle is a straight line through a vertex | |
| which cuts the corresponding angle in half. | |
| Returns | |
| ======= | |
| bisectors : dict | |
| Each key is a vertex (Point) and each value is the corresponding | |
| bisector (Segment). | |
| See Also | |
| ======== | |
| sympy.geometry.point.Point, sympy.geometry.line.Segment | |
| Examples | |
| ======== | |
| >>> from sympy import Point, Triangle, Segment | |
| >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1) | |
| >>> t = Triangle(p1, p2, p3) | |
| >>> from sympy import sqrt | |
| >>> t.bisectors()[p2] == Segment(Point(1, 0), Point(0, sqrt(2) - 1)) | |
| True | |
| """ | |
| # use lines containing sides so containment check during | |
| # intersection calculation can be avoided, thus reducing | |
| # the processing time for calculating the bisectors | |
| s = [Line(l) for l in self.sides] | |
| v = self.vertices | |
| c = self.incenter | |
| l1 = Segment(v[0], Line(v[0], c).intersection(s[1])[0]) | |
| l2 = Segment(v[1], Line(v[1], c).intersection(s[2])[0]) | |
| l3 = Segment(v[2], Line(v[2], c).intersection(s[0])[0]) | |
| return {v[0]: l1, v[1]: l2, v[2]: l3} | |
| def incenter(self): | |
| """The center of the incircle. | |
| The incircle is the circle which lies inside the triangle and touches | |
| all three sides. | |
| Returns | |
| ======= | |
| incenter : Point | |
| See Also | |
| ======== | |
| incircle, sympy.geometry.point.Point | |
| Examples | |
| ======== | |
| >>> from sympy import Point, Triangle | |
| >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1) | |
| >>> t = Triangle(p1, p2, p3) | |
| >>> t.incenter | |
| Point2D(1 - sqrt(2)/2, 1 - sqrt(2)/2) | |
| """ | |
| s = self.sides | |
| l = Matrix([s[i].length for i in [1, 2, 0]]) | |
| p = sum(l) | |
| v = self.vertices | |
| x = simplify(l.dot(Matrix([vi.x for vi in v]))/p) | |
| y = simplify(l.dot(Matrix([vi.y for vi in v]))/p) | |
| return Point(x, y) | |
| def inradius(self): | |
| """The radius of the incircle. | |
| Returns | |
| ======= | |
| inradius : number of Basic instance | |
| See Also | |
| ======== | |
| incircle, sympy.geometry.ellipse.Circle.radius | |
| Examples | |
| ======== | |
| >>> from sympy import Point, Triangle | |
| >>> p1, p2, p3 = Point(0, 0), Point(4, 0), Point(0, 3) | |
| >>> t = Triangle(p1, p2, p3) | |
| >>> t.inradius | |
| 1 | |
| """ | |
| return simplify(2 * self.area / self.perimeter) | |
| def incircle(self): | |
| """The incircle of the triangle. | |
| The incircle is the circle which lies inside the triangle and touches | |
| all three sides. | |
| Returns | |
| ======= | |
| incircle : Circle | |
| See Also | |
| ======== | |
| sympy.geometry.ellipse.Circle | |
| Examples | |
| ======== | |
| >>> from sympy import Point, Triangle | |
| >>> p1, p2, p3 = Point(0, 0), Point(2, 0), Point(0, 2) | |
| >>> t = Triangle(p1, p2, p3) | |
| >>> t.incircle | |
| Circle(Point2D(2 - sqrt(2), 2 - sqrt(2)), 2 - sqrt(2)) | |
| """ | |
| return Circle(self.incenter, self.inradius) | |
| def exradii(self): | |
| """The radius of excircles of a triangle. | |
| An excircle of the triangle is a circle lying outside the triangle, | |
| tangent to one of its sides and tangent to the extensions of the | |
| other two. | |
| Returns | |
| ======= | |
| exradii : dict | |
| See Also | |
| ======== | |
| sympy.geometry.polygon.Triangle.inradius | |
| Examples | |
| ======== | |
| The exradius touches the side of the triangle to which it is keyed, e.g. | |
| the exradius touching side 2 is: | |
| >>> from sympy import Point, Triangle | |
| >>> p1, p2, p3 = Point(0, 0), Point(6, 0), Point(0, 2) | |
| >>> t = Triangle(p1, p2, p3) | |
| >>> t.exradii[t.sides[2]] | |
| -2 + sqrt(10) | |
| References | |
| ========== | |
| .. [1] https://mathworld.wolfram.com/Exradius.html | |
| .. [2] https://mathworld.wolfram.com/Excircles.html | |
| """ | |
| side = self.sides | |
| a = side[0].length | |
| b = side[1].length | |
| c = side[2].length | |
| s = (a+b+c)/2 | |
| area = self.area | |
| exradii = {self.sides[0]: simplify(area/(s-a)), | |
| self.sides[1]: simplify(area/(s-b)), | |
| self.sides[2]: simplify(area/(s-c))} | |
| return exradii | |
| def excenters(self): | |
| """Excenters of the triangle. | |
| An excenter is the center of a circle that is tangent to a side of the | |
| triangle and the extensions of the other two sides. | |
| Returns | |
| ======= | |
| excenters : dict | |
| Examples | |
| ======== | |
| The excenters are keyed to the side of the triangle to which their corresponding | |
| excircle is tangent: The center is keyed, e.g. the excenter of a circle touching | |
| side 0 is: | |
| >>> from sympy import Point, Triangle | |
| >>> p1, p2, p3 = Point(0, 0), Point(6, 0), Point(0, 2) | |
| >>> t = Triangle(p1, p2, p3) | |
| >>> t.excenters[t.sides[0]] | |
| Point2D(12*sqrt(10), 2/3 + sqrt(10)/3) | |
| See Also | |
| ======== | |
| sympy.geometry.polygon.Triangle.exradii | |
| References | |
| ========== | |
| .. [1] https://mathworld.wolfram.com/Excircles.html | |
| """ | |
| s = self.sides | |
| v = self.vertices | |
| a = s[0].length | |
| b = s[1].length | |
| c = s[2].length | |
| x = [v[0].x, v[1].x, v[2].x] | |
| y = [v[0].y, v[1].y, v[2].y] | |
| exc_coords = { | |
| "x1": simplify(-a*x[0]+b*x[1]+c*x[2]/(-a+b+c)), | |
| "x2": simplify(a*x[0]-b*x[1]+c*x[2]/(a-b+c)), | |
| "x3": simplify(a*x[0]+b*x[1]-c*x[2]/(a+b-c)), | |
| "y1": simplify(-a*y[0]+b*y[1]+c*y[2]/(-a+b+c)), | |
| "y2": simplify(a*y[0]-b*y[1]+c*y[2]/(a-b+c)), | |
| "y3": simplify(a*y[0]+b*y[1]-c*y[2]/(a+b-c)) | |
| } | |
| excenters = { | |
| s[0]: Point(exc_coords["x1"], exc_coords["y1"]), | |
| s[1]: Point(exc_coords["x2"], exc_coords["y2"]), | |
| s[2]: Point(exc_coords["x3"], exc_coords["y3"]) | |
| } | |
| return excenters | |
| def medians(self): | |
| """The medians of the triangle. | |
| A median of a triangle is a straight line through a vertex and the | |
| midpoint of the opposite side, and divides the triangle into two | |
| equal areas. | |
| Returns | |
| ======= | |
| medians : dict | |
| Each key is a vertex (Point) and each value is the median (Segment) | |
| at that point. | |
| See Also | |
| ======== | |
| sympy.geometry.point.Point.midpoint, sympy.geometry.line.Segment.midpoint | |
| Examples | |
| ======== | |
| >>> from sympy import Point, Triangle | |
| >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1) | |
| >>> t = Triangle(p1, p2, p3) | |
| >>> t.medians[p1] | |
| Segment2D(Point2D(0, 0), Point2D(1/2, 1/2)) | |
| """ | |
| s = self.sides | |
| v = self.vertices | |
| return {v[0]: Segment(v[0], s[1].midpoint), | |
| v[1]: Segment(v[1], s[2].midpoint), | |
| v[2]: Segment(v[2], s[0].midpoint)} | |
| def medial(self): | |
| """The medial triangle of the triangle. | |
| The triangle which is formed from the midpoints of the three sides. | |
| Returns | |
| ======= | |
| medial : Triangle | |
| See Also | |
| ======== | |
| sympy.geometry.line.Segment.midpoint | |
| Examples | |
| ======== | |
| >>> from sympy import Point, Triangle | |
| >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1) | |
| >>> t = Triangle(p1, p2, p3) | |
| >>> t.medial | |
| Triangle(Point2D(1/2, 0), Point2D(1/2, 1/2), Point2D(0, 1/2)) | |
| """ | |
| s = self.sides | |
| return Triangle(s[0].midpoint, s[1].midpoint, s[2].midpoint) | |
| def nine_point_circle(self): | |
| """The nine-point circle of the triangle. | |
| Nine-point circle is the circumcircle of the medial triangle, which | |
| passes through the feet of altitudes and the middle points of segments | |
| connecting the vertices and the orthocenter. | |
| Returns | |
| ======= | |
| nine_point_circle : Circle | |
| See also | |
| ======== | |
| sympy.geometry.line.Segment.midpoint | |
| sympy.geometry.polygon.Triangle.medial | |
| sympy.geometry.polygon.Triangle.orthocenter | |
| Examples | |
| ======== | |
| >>> from sympy import Point, Triangle | |
| >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1) | |
| >>> t = Triangle(p1, p2, p3) | |
| >>> t.nine_point_circle | |
| Circle(Point2D(1/4, 1/4), sqrt(2)/4) | |
| """ | |
| return Circle(*self.medial.vertices) | |
| def eulerline(self): | |
| """The Euler line of the triangle. | |
| The line which passes through circumcenter, centroid and orthocenter. | |
| Returns | |
| ======= | |
| eulerline : Line (or Point for equilateral triangles in which case all | |
| centers coincide) | |
| Examples | |
| ======== | |
| >>> from sympy import Point, Triangle | |
| >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1) | |
| >>> t = Triangle(p1, p2, p3) | |
| >>> t.eulerline | |
| Line2D(Point2D(0, 0), Point2D(1/2, 1/2)) | |
| """ | |
| if self.is_equilateral(): | |
| return self.orthocenter | |
| return Line(self.orthocenter, self.circumcenter) | |
| def rad(d): | |
| """Return the radian value for the given degrees (pi = 180 degrees).""" | |
| return d*pi/180 | |
| def deg(r): | |
| """Return the degree value for the given radians (pi = 180 degrees).""" | |
| return r/pi*180 | |
| def _slope(d): | |
| rv = tan(rad(d)) | |
| return rv | |
| def _asa(d1, l, d2): | |
| """Return triangle having side with length l on the x-axis.""" | |
| xy = Line((0, 0), slope=_slope(d1)).intersection( | |
| Line((l, 0), slope=_slope(180 - d2)))[0] | |
| return Triangle((0, 0), (l, 0), xy) | |
| def _sss(l1, l2, l3): | |
| """Return triangle having side of length l1 on the x-axis.""" | |
| c1 = Circle((0, 0), l3) | |
| c2 = Circle((l1, 0), l2) | |
| inter = [a for a in c1.intersection(c2) if a.y.is_nonnegative] | |
| if not inter: | |
| return None | |
| pt = inter[0] | |
| return Triangle((0, 0), (l1, 0), pt) | |
| def _sas(l1, d, l2): | |
| """Return triangle having side with length l2 on the x-axis.""" | |
| p1 = Point(0, 0) | |
| p2 = Point(l2, 0) | |
| p3 = Point(cos(rad(d))*l1, sin(rad(d))*l1) | |
| return Triangle(p1, p2, p3) | |