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

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 

19 

20from mpmath.libmp.libmpf import prec_to_dps 

21 

22import warnings 

23 

24 

25x, y, T = [Dummy('polygon_dummy', real=True) for i in range(3)] 

26 

27 

28class Polygon(GeometrySet): 

29 """A two-dimensional polygon. 

30 

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. 

33 

34 Parameters 

35 ========== 

36 

37 vertices 

38 A sequence of points. 

39 

40 n : int, optional 

41 If $> 0$, an n-sided RegularPolygon is created. 

42 Default value is $0$. 

43 

44 Attributes 

45 ========== 

46 

47 area 

48 angles 

49 perimeter 

50 vertices 

51 centroid 

52 sides 

53 

54 Raises 

55 ====== 

56 

57 GeometryError 

58 If all parameters are not Points. 

59 

60 See Also 

61 ======== 

62 

63 sympy.geometry.point.Point, sympy.geometry.line.Segment, Triangle 

64 

65 Notes 

66 ===== 

67 

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. 

71 

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). 

75 

76 A Triangle, Segment or Point will be returned when there are 3 or 

77 fewer points provided. 

78 

79 Examples 

80 ======== 

81 

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)) 

90 

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. 

96 

97 >>> Polygon((0, 2), (2, 2), (0, 0), (2, 0)).area 

98 0 

99 

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. 

106 

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) 

117 

118 """ 

119 

120 __slots__ = () 

121 

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) 

131 

132 vertices = [Point(a, dim=2, **kwargs) for a in args] 

133 

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 

142 

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 

153 

154 vertices = list(nodup) 

155 

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) 

164 

165 @property 

166 def area(self): 

167 """ 

168 The area of the polygon. 

169 

170 Notes 

171 ===== 

172 

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. 

176 

177 See Also 

178 ======== 

179 

180 sympy.geometry.ellipse.Ellipse.area 

181 

182 Examples 

183 ======== 

184 

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 

190 

191 In the Z shaped polygon (with the lower right connecting back 

192 to the upper left) the areas cancel out: 

193 

194 >>> Z = Polygon((0, 1), (1, 1), (0, 0), (1, 0)) 

195 >>> Z.area 

196 0 

197 

198 In the M shaped polygon, areas do not cancel because no side 

199 crosses any other (though there is a point of contact). 

200 

201 >>> M = Polygon((0, 0), (0, 1), (2, 0), (3, 1), (3, 0)) 

202 >>> M.area 

203 -3/2 

204 

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 

213 

214 @staticmethod 

215 def _isright(a, b, c): 

216 """Return True/False for cw/ccw orientation. 

217 

218 Examples 

219 ======== 

220 

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 

235 

236 @property 

237 def angles(self): 

238 """The internal angle at each vertex. 

239 

240 Returns 

241 ======= 

242 

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. 

247 

248 See Also 

249 ======== 

250 

251 sympy.geometry.point.Point, sympy.geometry.line.LinearEntity.angle_between 

252 

253 Examples 

254 ======== 

255 

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) 

263 

264 """ 

265 

266 # Determine orientation of points 

267 args = self.vertices 

268 cw = self._isright(args[-1], args[0], args[1]) 

269 

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 

279 

280 @property 

281 def ambient_dimension(self): 

282 return self.vertices[0].ambient_dimension 

283 

284 @property 

285 def perimeter(self): 

286 """The perimeter of the polygon. 

287 

288 Returns 

289 ======= 

290 

291 perimeter : number or Basic instance 

292 

293 See Also 

294 ======== 

295 

296 sympy.geometry.line.Segment.length 

297 

298 Examples 

299 ======== 

300 

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) 

312 

313 @property 

314 def vertices(self): 

315 """The vertices of the polygon. 

316 

317 Returns 

318 ======= 

319 

320 vertices : list of Points 

321 

322 Notes 

323 ===== 

324 

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. 

329 

330 See Also 

331 ======== 

332 

333 sympy.geometry.point.Point 

334 

335 Examples 

336 ======== 

337 

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) 

345 

346 """ 

347 return list(self.args) 

348 

349 @property 

350 def centroid(self): 

351 """The centroid of the polygon. 

352 

353 Returns 

354 ======= 

355 

356 centroid : Point 

357 

358 See Also 

359 ======== 

360 

361 sympy.geometry.point.Point, sympy.geometry.util.centroid 

362 

363 Examples 

364 ======== 

365 

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) 

371 

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)) 

383 

384 

385 def second_moment_of_area(self, point=None): 

386 """Returns the second moment and product moment of area of a two dimensional polygon. 

387 

388 Parameters 

389 ========== 

390 

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. 

395 

396 Returns 

397 ======= 

398 

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. 

402 

403 Examples 

404 ======== 

405 

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) 

414 

415 References 

416 ========== 

417 

418 .. [1] https://en.wikipedia.org/wiki/Second_moment_of_area 

419 

420 """ 

421 

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 

440 

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))) 

444 

445 return I_xx, I_yy, I_xy 

446 

447 

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. 

452 

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. 

461 

462 References 

463 ========== 

464 

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 

467 

468 Parameters 

469 ========== 

470 

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. 

474 

475 Returns 

476 ======= 

477 

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 

483 

484 Examples 

485 ======== 

486 

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 

501 

502 h_line = Line(point, slope=0) 

503 v_line = Line(point, slope=S.Infinity) 

504 

505 h_poly = self.cut_section(h_line) 

506 v_poly = self.cut_section(v_line) 

507 

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] 

510 

511 Q_x = (poly_1.centroid.y - yc)*poly_1.area 

512 Q_y = (poly_2.centroid.x - xc)*poly_2.area 

513 

514 return Q_x, Q_y 

515 

516 

517 def polar_second_moment_of_area(self): 

518 """Returns the polar modulus of a two-dimensional polygon 

519 

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) 

528 

529 Examples 

530 ======== 

531 

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 

537 

538 References 

539 ========== 

540 

541 .. [1] https://en.wikipedia.org/wiki/Polar_moment_of_inertia 

542 

543 """ 

544 second_moment = self.second_moment_of_area() 

545 return second_moment[0] + second_moment[1] 

546 

547 

548 def section_modulus(self, point=None): 

549 """Returns a tuple with the section modulus of a two-dimensional 

550 polygon. 

551 

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. 

555 

556 Parameters 

557 ========== 

558 

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. 

563 

564 Returns 

565 ======= 

566 

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 

572 

573 Examples 

574 ======== 

575 

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) 

583 

584 References 

585 ========== 

586 

587 .. [1] https://en.wikipedia.org/wiki/Section_modulus 

588 

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 

600 

601 second_moment= self.second_moment_of_area() 

602 S_x = second_moment[0]/y 

603 S_y = second_moment[1]/x 

604 

605 return S_x, S_y 

606 

607 

608 @property 

609 def sides(self): 

610 """The directed line segments that form the sides of the polygon. 

611 

612 Returns 

613 ======= 

614 

615 sides : list of sides 

616 Each side is a directed Segment. 

617 

618 See Also 

619 ======== 

620 

621 sympy.geometry.point.Point, sympy.geometry.line.Segment 

622 

623 Examples 

624 ======== 

625 

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))] 

633 

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 

640 

641 @property 

642 def bounds(self): 

643 """Return a tuple (xmin, ymin, xmax, ymax) representing the bounding 

644 rectangle for the geometric figure. 

645 

646 """ 

647 

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)) 

652 

653 def is_convex(self): 

654 """Is the polygon convex? 

655 

656 A polygon is convex if all its interior angles are less than 180 

657 degrees and there are no intersections between sides. 

658 

659 Returns 

660 ======= 

661 

662 is_convex : boolean 

663 True if this polygon is convex, False otherwise. 

664 

665 See Also 

666 ======== 

667 

668 sympy.geometry.util.convex_hull 

669 

670 Examples 

671 ======== 

672 

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 

678 

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 

698 

699 def encloses_point(self, p): 

700 """ 

701 Return True if p is enclosed by (is inside of) self. 

702 

703 Notes 

704 ===== 

705 

706 Being on the border of self is considered False. 

707 

708 Parameters 

709 ========== 

710 

711 p : Point 

712 

713 Returns 

714 ======= 

715 

716 encloses_point : True, False or None 

717 

718 See Also 

719 ======== 

720 

721 sympy.geometry.point.Point, sympy.geometry.ellipse.Ellipse.encloses_point 

722 

723 Examples 

724 ======== 

725 

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 

734 

735 References 

736 ========== 

737 

738 .. [1] http://paulbourke.net/geometry/polygonmesh/#insidepoly 

739 

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 

744 

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 

751 

752 poly = Polygon(*lit) 

753 

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)) 

759 

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 

771 

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 

785 

786 def arbitrary_point(self, parameter='t'): 

787 """A parameterized point on the polygon. 

788 

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. 

793 

794 Parameters 

795 ========== 

796 

797 parameter : str, optional 

798 Default value is 't'. 

799 

800 Returns 

801 ======= 

802 

803 arbitrary_point : Point 

804 

805 Raises 

806 ====== 

807 

808 ValueError 

809 When `parameter` already appears in the Polygon's definition. 

810 

811 See Also 

812 ======== 

813 

814 sympy.geometry.point.Point 

815 

816 Examples 

817 ======== 

818 

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) 

827 

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) 

844 

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)) 

865 

866 def plot_interval(self, parameter='t'): 

867 """The plot interval for the default geometric plot of the polygon. 

868 

869 Parameters 

870 ========== 

871 

872 parameter : str, optional 

873 Default value is 't'. 

874 

875 Returns 

876 ======= 

877 

878 plot_interval : list (plot interval) 

879 [parameter, lower_bound, upper_bound] 

880 

881 Examples 

882 ======== 

883 

884 >>> from sympy import Polygon 

885 >>> p = Polygon((0, 0), (1, 0), (1, 1)) 

886 >>> p.plot_interval() 

887 [t, 0, 1] 

888 

889 """ 

890 t = Symbol(parameter, real=True) 

891 return [t, 0, 1] 

892 

893 def intersection(self, o): 

894 """The intersection of polygon and geometry entity. 

895 

896 The intersection may be empty and can contain individual Points and 

897 complete Line Segments. 

898 

899 Parameters 

900 ========== 

901 

902 other: GeometryEntity 

903 

904 Returns 

905 ======= 

906 

907 intersection : list 

908 The list of Segments and Points 

909 

910 See Also 

911 ======== 

912 

913 sympy.geometry.point.Point, sympy.geometry.line.Segment 

914 

915 Examples 

916 ======== 

917 

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)) 

935 

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)] 

939 

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)) 

948 

949 

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. 

954 

955 Parameters 

956 ========== 

957 

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. 

961 

962 Returns 

963 ======= 

964 

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. 

970 

971 Raises 

972 ====== 

973 

974 ValueError: When the line does not intersect the polygon 

975 

976 Examples 

977 ======== 

978 

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) 

994 

995 References 

996 ========== 

997 

998 .. [1] https://github.com/sympy/sympy/wiki/A-method-to-return-a-cut-section-of-any-polygon-geometry 

999 

1000 """ 

1001 intersection_points = self.intersection(line) 

1002 if not intersection_points: 

1003 raise ValueError("This line does not intersect the polygon") 

1004 

1005 points = list(self.vertices) 

1006 points.append(points[0]) 

1007 

1008 eq = line.equation(x, y) 

1009 

1010 # considering equation of line to be `ax +by + c` 

1011 a = eq.coeff(x) 

1012 b = eq.coeff(y) 

1013 

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 

1024 

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]) 

1034 

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 

1046 

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) 

1052 

1053 return upper_polygon, lower_polygon 

1054 

1055 

1056 def distance(self, o): 

1057 """ 

1058 Returns the shortest distance between self and o. 

1059 

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. 

1062 

1063 Examples 

1064 ======== 

1065 

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() 

1084 

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. 

1090 

1091 Notes 

1092 ===== 

1093 

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(). 

1097 

1098 See Also 

1099 ======== 

1100 

1101 sympy.geometry.point.Point.distance 

1102 

1103 Examples 

1104 ======== 

1105 

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 

1111 

1112 Description of method used 

1113 ========================== 

1114 

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 

1123 

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) 

1141 

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) 

1147 

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) 

1155 

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 = {} 

1162 

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] 

1168 

1169 if side.p2 in e1_connections: 

1170 e1_connections[side.p2].append(side.p1) 

1171 else: 

1172 e1_connections[side.p2] = [side.p1] 

1173 

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] 

1179 

1180 if side.p2 in e2_connections: 

1181 e2_connections[side.p2].append(side.p1) 

1182 else: 

1183 e2_connections[side.p2] = [side.p1] 

1184 

1185 e1_current = e1_ymax 

1186 e2_current = e2_ymin 

1187 support_line = Line(Point(S.Zero, S.Zero), Point(S.One, S.Zero)) 

1188 

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 

1206 

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 

1219 

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)) 

1228 

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) 

1233 

1234 if min_dist_current.evalf() < min_dist.evalf(): 

1235 min_dist = min_dist_current 

1236 

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) 

1247 

1248 if min_dist_current.evalf() < min_dist.evalf(): 

1249 min_dist = min_dist_current 

1250 

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) 

1263 

1264 min_dist_current = min(min1, min2) 

1265 if min_dist_current.evalf() < min_dist.evalf(): 

1266 min_dist = min_dist_current 

1267 

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] 

1274 

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 

1284 

1285 def _svg(self, scale_factor=1., fill_color="#66cc99"): 

1286 """Returns SVG path element for the Polygon. 

1287 

1288 Parameters 

1289 ========== 

1290 

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) 

1303 

1304 def _hashable_content(self): 

1305 

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] 

1313 

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) 

1324 

1325 def __contains__(self, o): 

1326 """ 

1327 Return True if o is contained within the boundary lines of self.altitudes 

1328 

1329 Parameters 

1330 ========== 

1331 

1332 other : GeometryEntity 

1333 

1334 Returns 

1335 ======= 

1336 

1337 contained in : bool 

1338 The points (and sides, if applicable) are contained in self. 

1339 

1340 See Also 

1341 ======== 

1342 

1343 sympy.geometry.entity.GeometryEntity.encloses 

1344 

1345 Examples 

1346 ======== 

1347 

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 

1361 

1362 """ 

1363 

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 

1374 

1375 return False 

1376 

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. 

1380 

1381 The distance between the points defining the bisector ray is 1. 

1382 

1383 Examples 

1384 ======== 

1385 

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 

1410 

1411 

1412class RegularPolygon(Polygon): 

1413 """ 

1414 A regular polygon. 

1415 

1416 Such a polygon has all internal angles equal and all sides the same length. 

1417 

1418 Parameters 

1419 ========== 

1420 

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 

1426 

1427 Attributes 

1428 ========== 

1429 

1430 vertices 

1431 center 

1432 radius 

1433 rotation 

1434 apothem 

1435 interior_angle 

1436 exterior_angle 

1437 circumcircle 

1438 incircle 

1439 angles 

1440 

1441 Raises 

1442 ====== 

1443 

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. 

1447 

1448 Notes 

1449 ===== 

1450 

1451 A RegularPolygon can be instantiated with Polygon with the kwarg n. 

1452 

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. 

1456 

1457 See Also 

1458 ======== 

1459 

1460 sympy.geometry.point.Point, Polygon 

1461 

1462 Examples 

1463 ======== 

1464 

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) 

1471 

1472 """ 

1473 

1474 __slots__ = ('_n', '_center', '_radius', '_rot') 

1475 

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) 

1485 

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 

1492 

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) 

1498 

1499 @property 

1500 def args(self): 

1501 """ 

1502 Returns the center point, the radius, 

1503 the number of sides, and the orientation angle. 

1504 

1505 Examples 

1506 ======== 

1507 

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 

1514 

1515 def __str__(self): 

1516 return 'RegularPolygon(%s, %s, %s, %s)' % tuple(self.args) 

1517 

1518 def __repr__(self): 

1519 return 'RegularPolygon(%s, %s, %s, %s)' % tuple(self.args) 

1520 

1521 @property 

1522 def area(self): 

1523 """Returns the area. 

1524 

1525 Examples 

1526 ======== 

1527 

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)) 

1537 

1538 @property 

1539 def length(self): 

1540 """Returns the length of the sides. 

1541 

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. 

1545 

1546 Examples 

1547 ======== 

1548 

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 

1556 

1557 """ 

1558 return self.radius*2*sin(pi/self._n) 

1559 

1560 @property 

1561 def center(self): 

1562 """The center of the RegularPolygon 

1563 

1564 This is also the center of the circumscribing circle. 

1565 

1566 Returns 

1567 ======= 

1568 

1569 center : Point 

1570 

1571 See Also 

1572 ======== 

1573 

1574 sympy.geometry.point.Point, sympy.geometry.ellipse.Ellipse.center 

1575 

1576 Examples 

1577 ======== 

1578 

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 

1585 

1586 centroid = center 

1587 

1588 @property 

1589 def circumcenter(self): 

1590 """ 

1591 Alias for center. 

1592 

1593 Examples 

1594 ======== 

1595 

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 

1602 

1603 @property 

1604 def radius(self): 

1605 """Radius of the RegularPolygon 

1606 

1607 This is also the radius of the circumscribing circle. 

1608 

1609 Returns 

1610 ======= 

1611 

1612 radius : number or instance of Basic 

1613 

1614 See Also 

1615 ======== 

1616 

1617 sympy.geometry.line.Segment.length, sympy.geometry.ellipse.Circle.radius 

1618 

1619 Examples 

1620 ======== 

1621 

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 

1628 

1629 """ 

1630 return self._radius 

1631 

1632 @property 

1633 def circumradius(self): 

1634 """ 

1635 Alias for radius. 

1636 

1637 Examples 

1638 ======== 

1639 

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 

1648 

1649 @property 

1650 def rotation(self): 

1651 """CCW angle by which the RegularPolygon is rotated 

1652 

1653 Returns 

1654 ======= 

1655 

1656 rotation : number or instance of Basic 

1657 

1658 Examples 

1659 ======== 

1660 

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 

1666 

1667 Numerical rotation angles are made canonical: 

1668 

1669 >>> RegularPolygon(Point(0, 0), 3, 4, a).rotation 

1670 a 

1671 >>> RegularPolygon(Point(0, 0), 3, 4, pi).rotation 

1672 0 

1673 

1674 """ 

1675 return self._rot 

1676 

1677 @property 

1678 def apothem(self): 

1679 """The inradius of the RegularPolygon. 

1680 

1681 The apothem/inradius is the radius of the inscribed circle. 

1682 

1683 Returns 

1684 ======= 

1685 

1686 apothem : number or instance of Basic 

1687 

1688 See Also 

1689 ======== 

1690 

1691 sympy.geometry.line.Segment.length, sympy.geometry.ellipse.Circle.radius 

1692 

1693 Examples 

1694 ======== 

1695 

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 

1702 

1703 """ 

1704 return self.radius * cos(S.Pi/self._n) 

1705 

1706 @property 

1707 def inradius(self): 

1708 """ 

1709 Alias for apothem. 

1710 

1711 Examples 

1712 ======== 

1713 

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 

1722 

1723 @property 

1724 def interior_angle(self): 

1725 """Measure of the interior angles. 

1726 

1727 Returns 

1728 ======= 

1729 

1730 interior_angle : number 

1731 

1732 See Also 

1733 ======== 

1734 

1735 sympy.geometry.line.LinearEntity.angle_between 

1736 

1737 Examples 

1738 ======== 

1739 

1740 >>> from sympy import RegularPolygon, Point 

1741 >>> rp = RegularPolygon(Point(0, 0), 4, 8) 

1742 >>> rp.interior_angle 

1743 3*pi/4 

1744 

1745 """ 

1746 return (self._n - 2)*S.Pi/self._n 

1747 

1748 @property 

1749 def exterior_angle(self): 

1750 """Measure of the exterior angles. 

1751 

1752 Returns 

1753 ======= 

1754 

1755 exterior_angle : number 

1756 

1757 See Also 

1758 ======== 

1759 

1760 sympy.geometry.line.LinearEntity.angle_between 

1761 

1762 Examples 

1763 ======== 

1764 

1765 >>> from sympy import RegularPolygon, Point 

1766 >>> rp = RegularPolygon(Point(0, 0), 4, 8) 

1767 >>> rp.exterior_angle 

1768 pi/4 

1769 

1770 """ 

1771 return 2*S.Pi/self._n 

1772 

1773 @property 

1774 def circumcircle(self): 

1775 """The circumcircle of the RegularPolygon. 

1776 

1777 Returns 

1778 ======= 

1779 

1780 circumcircle : Circle 

1781 

1782 See Also 

1783 ======== 

1784 

1785 circumcenter, sympy.geometry.ellipse.Circle 

1786 

1787 Examples 

1788 ======== 

1789 

1790 >>> from sympy import RegularPolygon, Point 

1791 >>> rp = RegularPolygon(Point(0, 0), 4, 8) 

1792 >>> rp.circumcircle 

1793 Circle(Point2D(0, 0), 4) 

1794 

1795 """ 

1796 return Circle(self.center, self.radius) 

1797 

1798 @property 

1799 def incircle(self): 

1800 """The incircle of the RegularPolygon. 

1801 

1802 Returns 

1803 ======= 

1804 

1805 incircle : Circle 

1806 

1807 See Also 

1808 ======== 

1809 

1810 inradius, sympy.geometry.ellipse.Circle 

1811 

1812 Examples 

1813 ======== 

1814 

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)) 

1819 

1820 """ 

1821 return Circle(self.center, self.apothem) 

1822 

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. 

1828 

1829 Examples 

1830 ======== 

1831 

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 

1844 

1845 def encloses_point(self, p): 

1846 """ 

1847 Return True if p is enclosed by (is inside of) self. 

1848 

1849 Notes 

1850 ===== 

1851 

1852 Being on the border of self is considered False. 

1853 

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. 

1857 

1858 Parameters 

1859 ========== 

1860 

1861 p : Point 

1862 

1863 Returns 

1864 ======= 

1865 

1866 encloses_point : True, False or None 

1867 

1868 See Also 

1869 ======== 

1870 

1871 sympy.geometry.ellipse.Ellipse.encloses_point 

1872 

1873 Examples 

1874 ======== 

1875 

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 

1890 

1891 """ 

1892 

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) 

1902 

1903 def spin(self, angle): 

1904 """Increment *in place* the virtual Polygon's rotation by ccw angle. 

1905 

1906 See also: rotate method which moves the center. 

1907 

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) 

1915 

1916 See Also 

1917 ======== 

1918 

1919 rotation 

1920 rotate : Creates a copy of the RegularPolygon rotated about a Point 

1921 

1922 """ 

1923 self._rot += angle 

1924 

1925 def rotate(self, angle, pt=None): 

1926 """Override GeometryEntity.rotate to first rotate the RegularPolygon 

1927 about its center. 

1928 

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) 

1935 

1936 See Also 

1937 ======== 

1938 

1939 rotation 

1940 spin : Rotates a RegularPolygon in place 

1941 

1942 """ 

1943 

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) 

1947 

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. 

1951 

1952 >>> from sympy import RegularPolygon 

1953 

1954 Symmetric scaling returns a RegularPolygon: 

1955 

1956 >>> RegularPolygon((0, 0), 1, 4).scale(2, 2) 

1957 RegularPolygon(Point2D(0, 0), 2, 4, 0) 

1958 

1959 Asymmetric scaling returns a kite as a Polygon: 

1960 

1961 >>> RegularPolygon((0, 0), 1, 4).scale(2, 1) 

1962 Polygon(Point2D(2, 0), Point2D(0, 1), Point2D(-2, 0), Point2D(0, -1)) 

1963 

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) 

1973 

1974 def reflect(self, line): 

1975 """Override GeometryEntity.reflect since this is not made of only 

1976 points. 

1977 

1978 Examples 

1979 ======== 

1980 

1981 >>> from sympy import RegularPolygon, Line 

1982 

1983 >>> RegularPolygon((0, 0), 1, 4).reflect(Line((0, 1), slope=-2)) 

1984 RegularPolygon(Point2D(4/5, 2/5), -1, 4, atan(4/3)) 

1985 

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) 

2001 

2002 @property 

2003 def vertices(self): 

2004 """The vertices of the RegularPolygon. 

2005 

2006 Returns 

2007 ======= 

2008 

2009 vertices : list 

2010 Each vertex is a Point. 

2011 

2012 See Also 

2013 ======== 

2014 

2015 sympy.geometry.point.Point 

2016 

2017 Examples 

2018 ======== 

2019 

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)] 

2024 

2025 """ 

2026 c = self._center 

2027 r = abs(self._radius) 

2028 rot = self._rot 

2029 v = 2*S.Pi/self._n 

2030 

2031 return [Point(c.x + r*cos(k*v + rot), c.y + r*sin(k*v + rot)) 

2032 for k in range(self._n)] 

2033 

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 

2040 

2041 def __hash__(self): 

2042 return super().__hash__() 

2043 

2044 

2045class Triangle(Polygon): 

2046 """ 

2047 A polygon with three vertices and three sides. 

2048 

2049 Parameters 

2050 ========== 

2051 

2052 points : sequence of Points 

2053 keyword: asa, sas, or sss to specify sides/angles of the triangle 

2054 

2055 Attributes 

2056 ========== 

2057 

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 

2070 

2071 Raises 

2072 ====== 

2073 

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. 

2077 

2078 See Also 

2079 ======== 

2080 

2081 sympy.geometry.point.Point, Polygon 

2082 

2083 Examples 

2084 ======== 

2085 

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)) 

2089 

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: 

2093 

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)) 

2100 

2101 """ 

2102 

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) 

2113 

2114 vertices = [Point(a, dim=2, **kwargs) for a in args] 

2115 

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 

2124 

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 

2135 

2136 vertices = list(filter(lambda x: x is not None, nodup)) 

2137 

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) 

2144 

2145 @property 

2146 def vertices(self): 

2147 """The triangle's vertices 

2148 

2149 Returns 

2150 ======= 

2151 

2152 vertices : tuple 

2153 Each element in the tuple is a Point 

2154 

2155 See Also 

2156 ======== 

2157 

2158 sympy.geometry.point.Point 

2159 

2160 Examples 

2161 ======== 

2162 

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)) 

2167 

2168 """ 

2169 return self.args 

2170 

2171 def is_similar(t1, t2): 

2172 """Is another triangle similar to this one. 

2173 

2174 Two triangles are similar if one can be uniformly scaled to the other. 

2175 

2176 Parameters 

2177 ========== 

2178 

2179 other: Triangle 

2180 

2181 Returns 

2182 ======= 

2183 

2184 is_similar : boolean 

2185 

2186 See Also 

2187 ======== 

2188 

2189 sympy.geometry.entity.GeometryEntity.is_similar 

2190 

2191 Examples 

2192 ======== 

2193 

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 

2199 

2200 >>> t2 = Triangle(Point(0, 0), Point(-4, 0), Point(-4, -4)) 

2201 >>> t1.is_similar(t2) 

2202 False 

2203 

2204 """ 

2205 if not isinstance(t2, Polygon): 

2206 return False 

2207 

2208 s1_1, s1_2, s1_3 = [side.length for side in t1.sides] 

2209 s2 = [side.length for side in t2.sides] 

2210 

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) 

2216 

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) 

2224 

2225 def is_equilateral(self): 

2226 """Are all the sides the same length? 

2227 

2228 Returns 

2229 ======= 

2230 

2231 is_equilateral : boolean 

2232 

2233 See Also 

2234 ======== 

2235 

2236 sympy.geometry.entity.GeometryEntity.is_similar, RegularPolygon 

2237 is_isosceles, is_right, is_scalene 

2238 

2239 Examples 

2240 ======== 

2241 

2242 >>> from sympy import Triangle, Point 

2243 >>> t1 = Triangle(Point(0, 0), Point(4, 0), Point(4, 3)) 

2244 >>> t1.is_equilateral() 

2245 False 

2246 

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 

2251 

2252 """ 

2253 return not has_variety(s.length for s in self.sides) 

2254 

2255 def is_isosceles(self): 

2256 """Are two or more of the sides the same length? 

2257 

2258 Returns 

2259 ======= 

2260 

2261 is_isosceles : boolean 

2262 

2263 See Also 

2264 ======== 

2265 

2266 is_equilateral, is_right, is_scalene 

2267 

2268 Examples 

2269 ======== 

2270 

2271 >>> from sympy import Triangle, Point 

2272 >>> t1 = Triangle(Point(0, 0), Point(4, 0), Point(2, 4)) 

2273 >>> t1.is_isosceles() 

2274 True 

2275 

2276 """ 

2277 return has_dups(s.length for s in self.sides) 

2278 

2279 def is_scalene(self): 

2280 """Are all the sides of the triangle of different lengths? 

2281 

2282 Returns 

2283 ======= 

2284 

2285 is_scalene : boolean 

2286 

2287 See Also 

2288 ======== 

2289 

2290 is_equilateral, is_isosceles, is_right 

2291 

2292 Examples 

2293 ======== 

2294 

2295 >>> from sympy import Triangle, Point 

2296 >>> t1 = Triangle(Point(0, 0), Point(4, 0), Point(1, 4)) 

2297 >>> t1.is_scalene() 

2298 True 

2299 

2300 """ 

2301 return not has_dups(s.length for s in self.sides) 

2302 

2303 def is_right(self): 

2304 """Is the triangle right-angled. 

2305 

2306 Returns 

2307 ======= 

2308 

2309 is_right : boolean 

2310 

2311 See Also 

2312 ======== 

2313 

2314 sympy.geometry.line.LinearEntity.is_perpendicular 

2315 is_equilateral, is_isosceles, is_scalene 

2316 

2317 Examples 

2318 ======== 

2319 

2320 >>> from sympy import Triangle, Point 

2321 >>> t1 = Triangle(Point(0, 0), Point(4, 0), Point(4, 3)) 

2322 >>> t1.is_right() 

2323 True 

2324 

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]) 

2330 

2331 @property 

2332 def altitudes(self): 

2333 """The altitudes of the triangle. 

2334 

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. 

2338 

2339 Returns 

2340 ======= 

2341 

2342 altitudes : dict 

2343 The dictionary consists of keys which are vertices and values 

2344 which are Segments. 

2345 

2346 See Also 

2347 ======== 

2348 

2349 sympy.geometry.point.Point, sympy.geometry.line.Segment.length 

2350 

2351 Examples 

2352 ======== 

2353 

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)) 

2359 

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])} 

2366 

2367 @property 

2368 def orthocenter(self): 

2369 """The orthocenter of the triangle. 

2370 

2371 The orthocenter is the intersection of the altitudes of a triangle. 

2372 It may lie inside, outside or on the triangle. 

2373 

2374 Returns 

2375 ======= 

2376 

2377 orthocenter : Point 

2378 

2379 See Also 

2380 ======== 

2381 

2382 sympy.geometry.point.Point 

2383 

2384 Examples 

2385 ======== 

2386 

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) 

2392 

2393 """ 

2394 a = self.altitudes 

2395 v = self.vertices 

2396 return Line(a[v[0]]).intersection(Line(a[v[1]]))[0] 

2397 

2398 @property 

2399 def circumcenter(self): 

2400 """The circumcenter of the triangle 

2401 

2402 The circumcenter is the center of the circumcircle. 

2403 

2404 Returns 

2405 ======= 

2406 

2407 circumcenter : Point 

2408 

2409 See Also 

2410 ======== 

2411 

2412 sympy.geometry.point.Point 

2413 

2414 Examples 

2415 ======== 

2416 

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] 

2425 

2426 @property 

2427 def circumradius(self): 

2428 """The radius of the circumcircle of the triangle. 

2429 

2430 Returns 

2431 ======= 

2432 

2433 circumradius : number of Basic instance 

2434 

2435 See Also 

2436 ======== 

2437 

2438 sympy.geometry.ellipse.Circle.radius 

2439 

2440 Examples 

2441 ======== 

2442 

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]) 

2452 

2453 @property 

2454 def circumcircle(self): 

2455 """The circle which passes through the three vertices of the triangle. 

2456 

2457 Returns 

2458 ======= 

2459 

2460 circumcircle : Circle 

2461 

2462 See Also 

2463 ======== 

2464 

2465 sympy.geometry.ellipse.Circle 

2466 

2467 Examples 

2468 ======== 

2469 

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) 

2475 

2476 """ 

2477 return Circle(self.circumcenter, self.circumradius) 

2478 

2479 def bisectors(self): 

2480 """The angle bisectors of the triangle. 

2481 

2482 An angle bisector of a triangle is a straight line through a vertex 

2483 which cuts the corresponding angle in half. 

2484 

2485 Returns 

2486 ======= 

2487 

2488 bisectors : dict 

2489 Each key is a vertex (Point) and each value is the corresponding 

2490 bisector (Segment). 

2491 

2492 See Also 

2493 ======== 

2494 

2495 sympy.geometry.point.Point, sympy.geometry.line.Segment 

2496 

2497 Examples 

2498 ======== 

2499 

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 

2506 

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} 

2518 

2519 @property 

2520 def incenter(self): 

2521 """The center of the incircle. 

2522 

2523 The incircle is the circle which lies inside the triangle and touches 

2524 all three sides. 

2525 

2526 Returns 

2527 ======= 

2528 

2529 incenter : Point 

2530 

2531 See Also 

2532 ======== 

2533 

2534 incircle, sympy.geometry.point.Point 

2535 

2536 Examples 

2537 ======== 

2538 

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) 

2544 

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) 

2553 

2554 @property 

2555 def inradius(self): 

2556 """The radius of the incircle. 

2557 

2558 Returns 

2559 ======= 

2560 

2561 inradius : number of Basic instance 

2562 

2563 See Also 

2564 ======== 

2565 

2566 incircle, sympy.geometry.ellipse.Circle.radius 

2567 

2568 Examples 

2569 ======== 

2570 

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 

2576 

2577 """ 

2578 return simplify(2 * self.area / self.perimeter) 

2579 

2580 @property 

2581 def incircle(self): 

2582 """The incircle of the triangle. 

2583 

2584 The incircle is the circle which lies inside the triangle and touches 

2585 all three sides. 

2586 

2587 Returns 

2588 ======= 

2589 

2590 incircle : Circle 

2591 

2592 See Also 

2593 ======== 

2594 

2595 sympy.geometry.ellipse.Circle 

2596 

2597 Examples 

2598 ======== 

2599 

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)) 

2605 

2606 """ 

2607 return Circle(self.incenter, self.inradius) 

2608 

2609 @property 

2610 def exradii(self): 

2611 """The radius of excircles of a triangle. 

2612 

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. 

2616 

2617 Returns 

2618 ======= 

2619 

2620 exradii : dict 

2621 

2622 See Also 

2623 ======== 

2624 

2625 sympy.geometry.polygon.Triangle.inradius 

2626 

2627 Examples 

2628 ======== 

2629 

2630 The exradius touches the side of the triangle to which it is keyed, e.g. 

2631 the exradius touching side 2 is: 

2632 

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) 

2638 

2639 References 

2640 ========== 

2641 

2642 .. [1] https://mathworld.wolfram.com/Exradius.html 

2643 .. [2] https://mathworld.wolfram.com/Excircles.html 

2644 

2645 """ 

2646 

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))} 

2656 

2657 return exradii 

2658 

2659 @property 

2660 def excenters(self): 

2661 """Excenters of the triangle. 

2662 

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. 

2665 

2666 Returns 

2667 ======= 

2668 

2669 excenters : dict 

2670 

2671 

2672 Examples 

2673 ======== 

2674 

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: 

2678 

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) 

2684 

2685 See Also 

2686 ======== 

2687 

2688 sympy.geometry.polygon.Triangle.exradii 

2689 

2690 References 

2691 ========== 

2692 

2693 .. [1] https://mathworld.wolfram.com/Excircles.html 

2694 

2695 """ 

2696 

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] 

2704 

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 } 

2713 

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 } 

2719 

2720 return excenters 

2721 

2722 @property 

2723 def medians(self): 

2724 """The medians of the triangle. 

2725 

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. 

2729 

2730 Returns 

2731 ======= 

2732 

2733 medians : dict 

2734 Each key is a vertex (Point) and each value is the median (Segment) 

2735 at that point. 

2736 

2737 See Also 

2738 ======== 

2739 

2740 sympy.geometry.point.Point.midpoint, sympy.geometry.line.Segment.midpoint 

2741 

2742 Examples 

2743 ======== 

2744 

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)) 

2750 

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)} 

2757 

2758 @property 

2759 def medial(self): 

2760 """The medial triangle of the triangle. 

2761 

2762 The triangle which is formed from the midpoints of the three sides. 

2763 

2764 Returns 

2765 ======= 

2766 

2767 medial : Triangle 

2768 

2769 See Also 

2770 ======== 

2771 

2772 sympy.geometry.line.Segment.midpoint 

2773 

2774 Examples 

2775 ======== 

2776 

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)) 

2782 

2783 """ 

2784 s = self.sides 

2785 return Triangle(s[0].midpoint, s[1].midpoint, s[2].midpoint) 

2786 

2787 @property 

2788 def nine_point_circle(self): 

2789 """The nine-point circle of the triangle. 

2790 

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. 

2794 

2795 Returns 

2796 ======= 

2797 

2798 nine_point_circle : Circle 

2799 

2800 See also 

2801 ======== 

2802 

2803 sympy.geometry.line.Segment.midpoint 

2804 sympy.geometry.polygon.Triangle.medial 

2805 sympy.geometry.polygon.Triangle.orthocenter 

2806 

2807 Examples 

2808 ======== 

2809 

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) 

2815 

2816 """ 

2817 return Circle(*self.medial.vertices) 

2818 

2819 @property 

2820 def eulerline(self): 

2821 """The Euler line of the triangle. 

2822 

2823 The line which passes through circumcenter, centroid and orthocenter. 

2824 

2825 Returns 

2826 ======= 

2827 

2828 eulerline : Line (or Point for equilateral triangles in which case all 

2829 centers coincide) 

2830 

2831 Examples 

2832 ======== 

2833 

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)) 

2839 

2840 """ 

2841 if self.is_equilateral(): 

2842 return self.orthocenter 

2843 return Line(self.orthocenter, self.circumcenter) 

2844 

2845def rad(d): 

2846 """Return the radian value for the given degrees (pi = 180 degrees).""" 

2847 return d*pi/180 

2848 

2849 

2850def deg(r): 

2851 """Return the degree value for the given radians (pi = 180 degrees).""" 

2852 return r/pi*180 

2853 

2854 

2855def _slope(d): 

2856 rv = tan(rad(d)) 

2857 return rv 

2858 

2859 

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) 

2865 

2866 

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) 

2876 

2877 

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)