Coverage for /usr/lib/python3/dist-packages/sympy/geometry/line.py: 19%

650 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-14 15:55 +0200

1"""Line-like geometrical entities. 

2 

3Contains 

4======== 

5LinearEntity 

6Line 

7Ray 

8Segment 

9LinearEntity2D 

10Line2D 

11Ray2D 

12Segment2D 

13LinearEntity3D 

14Line3D 

15Ray3D 

16Segment3D 

17 

18""" 

19 

20from sympy.core.containers import Tuple 

21from sympy.core.evalf import N 

22from sympy.core.expr import Expr 

23from sympy.core.numbers import Rational, oo, Float 

24from sympy.core.relational import Eq 

25from sympy.core.singleton import S 

26from sympy.core.sorting import ordered 

27from sympy.core.symbol import _symbol, Dummy, uniquely_named_symbol 

28from sympy.core.sympify import sympify 

29from sympy.functions.elementary.piecewise import Piecewise 

30from sympy.functions.elementary.trigonometric import (_pi_coeff, acos, tan, atan2) 

31from .entity import GeometryEntity, GeometrySet 

32from .exceptions import GeometryError 

33from .point import Point, Point3D 

34from .util import find, intersection 

35from sympy.logic.boolalg import And 

36from sympy.matrices import Matrix 

37from sympy.sets.sets import Intersection 

38from sympy.simplify.simplify import simplify 

39from sympy.solvers.solvers import solve 

40from sympy.solvers.solveset import linear_coeffs 

41from sympy.utilities.misc import Undecidable, filldedent 

42 

43 

44import random 

45 

46 

47t, u = [Dummy('line_dummy') for i in range(2)] 

48 

49 

50class LinearEntity(GeometrySet): 

51 """A base class for all linear entities (Line, Ray and Segment) 

52 in n-dimensional Euclidean space. 

53 

54 Attributes 

55 ========== 

56 

57 ambient_dimension 

58 direction 

59 length 

60 p1 

61 p2 

62 points 

63 

64 Notes 

65 ===== 

66 

67 This is an abstract class and is not meant to be instantiated. 

68 

69 See Also 

70 ======== 

71 

72 sympy.geometry.entity.GeometryEntity 

73 

74 """ 

75 def __new__(cls, p1, p2=None, **kwargs): 

76 p1, p2 = Point._normalize_dimension(p1, p2) 

77 if p1 == p2: 

78 # sometimes we return a single point if we are not given two unique 

79 # points. This is done in the specific subclass 

80 raise ValueError( 

81 "%s.__new__ requires two unique Points." % cls.__name__) 

82 if len(p1) != len(p2): 

83 raise ValueError( 

84 "%s.__new__ requires two Points of equal dimension." % cls.__name__) 

85 

86 return GeometryEntity.__new__(cls, p1, p2, **kwargs) 

87 

88 def __contains__(self, other): 

89 """Return a definitive answer or else raise an error if it cannot 

90 be determined that other is on the boundaries of self.""" 

91 result = self.contains(other) 

92 

93 if result is not None: 

94 return result 

95 else: 

96 raise Undecidable( 

97 "Cannot decide whether '%s' contains '%s'" % (self, other)) 

98 

99 def _span_test(self, other): 

100 """Test whether the point `other` lies in the positive span of `self`. 

101 A point x is 'in front' of a point y if x.dot(y) >= 0. Return 

102 -1 if `other` is behind `self.p1`, 0 if `other` is `self.p1` and 

103 and 1 if `other` is in front of `self.p1`.""" 

104 if self.p1 == other: 

105 return 0 

106 

107 rel_pos = other - self.p1 

108 d = self.direction 

109 if d.dot(rel_pos) > 0: 

110 return 1 

111 return -1 

112 

113 @property 

114 def ambient_dimension(self): 

115 """A property method that returns the dimension of LinearEntity 

116 object. 

117 

118 Parameters 

119 ========== 

120 

121 p1 : LinearEntity 

122 

123 Returns 

124 ======= 

125 

126 dimension : integer 

127 

128 Examples 

129 ======== 

130 

131 >>> from sympy import Point, Line 

132 >>> p1, p2 = Point(0, 0), Point(1, 1) 

133 >>> l1 = Line(p1, p2) 

134 >>> l1.ambient_dimension 

135 2 

136 

137 >>> from sympy import Point, Line 

138 >>> p1, p2 = Point(0, 0, 0), Point(1, 1, 1) 

139 >>> l1 = Line(p1, p2) 

140 >>> l1.ambient_dimension 

141 3 

142 

143 """ 

144 return len(self.p1) 

145 

146 def angle_between(l1, l2): 

147 """Return the non-reflex angle formed by rays emanating from 

148 the origin with directions the same as the direction vectors 

149 of the linear entities. 

150 

151 Parameters 

152 ========== 

153 

154 l1 : LinearEntity 

155 l2 : LinearEntity 

156 

157 Returns 

158 ======= 

159 

160 angle : angle in radians 

161 

162 Notes 

163 ===== 

164 

165 From the dot product of vectors v1 and v2 it is known that: 

166 

167 ``dot(v1, v2) = |v1|*|v2|*cos(A)`` 

168 

169 where A is the angle formed between the two vectors. We can 

170 get the directional vectors of the two lines and readily 

171 find the angle between the two using the above formula. 

172 

173 See Also 

174 ======== 

175 

176 is_perpendicular, Ray2D.closing_angle 

177 

178 Examples 

179 ======== 

180 

181 >>> from sympy import Line 

182 >>> e = Line((0, 0), (1, 0)) 

183 >>> ne = Line((0, 0), (1, 1)) 

184 >>> sw = Line((1, 1), (0, 0)) 

185 >>> ne.angle_between(e) 

186 pi/4 

187 >>> sw.angle_between(e) 

188 3*pi/4 

189 

190 To obtain the non-obtuse angle at the intersection of lines, use 

191 the ``smallest_angle_between`` method: 

192 

193 >>> sw.smallest_angle_between(e) 

194 pi/4 

195 

196 >>> from sympy import Point3D, Line3D 

197 >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(1, 1, 1), Point3D(-1, 2, 0) 

198 >>> l1, l2 = Line3D(p1, p2), Line3D(p2, p3) 

199 >>> l1.angle_between(l2) 

200 acos(-sqrt(2)/3) 

201 >>> l1.smallest_angle_between(l2) 

202 acos(sqrt(2)/3) 

203 """ 

204 if not isinstance(l1, LinearEntity) and not isinstance(l2, LinearEntity): 

205 raise TypeError('Must pass only LinearEntity objects') 

206 

207 v1, v2 = l1.direction, l2.direction 

208 return acos(v1.dot(v2)/(abs(v1)*abs(v2))) 

209 

210 def smallest_angle_between(l1, l2): 

211 """Return the smallest angle formed at the intersection of the 

212 lines containing the linear entities. 

213 

214 Parameters 

215 ========== 

216 

217 l1 : LinearEntity 

218 l2 : LinearEntity 

219 

220 Returns 

221 ======= 

222 

223 angle : angle in radians 

224 

225 Examples 

226 ======== 

227 

228 >>> from sympy import Point, Line 

229 >>> p1, p2, p3 = Point(0, 0), Point(0, 4), Point(2, -2) 

230 >>> l1, l2 = Line(p1, p2), Line(p1, p3) 

231 >>> l1.smallest_angle_between(l2) 

232 pi/4 

233 

234 See Also 

235 ======== 

236 

237 angle_between, is_perpendicular, Ray2D.closing_angle 

238 """ 

239 if not isinstance(l1, LinearEntity) and not isinstance(l2, LinearEntity): 

240 raise TypeError('Must pass only LinearEntity objects') 

241 

242 v1, v2 = l1.direction, l2.direction 

243 return acos(abs(v1.dot(v2))/(abs(v1)*abs(v2))) 

244 

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

246 """A parameterized point on the Line. 

247 

248 Parameters 

249 ========== 

250 

251 parameter : str, optional 

252 The name of the parameter which will be used for the parametric 

253 point. The default value is 't'. When this parameter is 0, the 

254 first point used to define the line will be returned, and when 

255 it is 1 the second point will be returned. 

256 

257 Returns 

258 ======= 

259 

260 point : Point 

261 

262 Raises 

263 ====== 

264 

265 ValueError 

266 When ``parameter`` already appears in the Line's definition. 

267 

268 See Also 

269 ======== 

270 

271 sympy.geometry.point.Point 

272 

273 Examples 

274 ======== 

275 

276 >>> from sympy import Point, Line 

277 >>> p1, p2 = Point(1, 0), Point(5, 3) 

278 >>> l1 = Line(p1, p2) 

279 >>> l1.arbitrary_point() 

280 Point2D(4*t + 1, 3*t) 

281 >>> from sympy import Point3D, Line3D 

282 >>> p1, p2 = Point3D(1, 0, 0), Point3D(5, 3, 1) 

283 >>> l1 = Line3D(p1, p2) 

284 >>> l1.arbitrary_point() 

285 Point3D(4*t + 1, 3*t, t) 

286 

287 """ 

288 t = _symbol(parameter, real=True) 

289 if t.name in (f.name for f in self.free_symbols): 

290 raise ValueError(filldedent(''' 

291 Symbol %s already appears in object 

292 and cannot be used as a parameter. 

293 ''' % t.name)) 

294 # multiply on the right so the variable gets 

295 # combined with the coordinates of the point 

296 return self.p1 + (self.p2 - self.p1)*t 

297 

298 @staticmethod 

299 def are_concurrent(*lines): 

300 """Is a sequence of linear entities concurrent? 

301 

302 Two or more linear entities are concurrent if they all 

303 intersect at a single point. 

304 

305 Parameters 

306 ========== 

307 

308 lines 

309 A sequence of linear entities. 

310 

311 Returns 

312 ======= 

313 

314 True : if the set of linear entities intersect in one point 

315 False : otherwise. 

316 

317 See Also 

318 ======== 

319 

320 sympy.geometry.util.intersection 

321 

322 Examples 

323 ======== 

324 

325 >>> from sympy import Point, Line 

326 >>> p1, p2 = Point(0, 0), Point(3, 5) 

327 >>> p3, p4 = Point(-2, -2), Point(0, 2) 

328 >>> l1, l2, l3 = Line(p1, p2), Line(p1, p3), Line(p1, p4) 

329 >>> Line.are_concurrent(l1, l2, l3) 

330 True 

331 >>> l4 = Line(p2, p3) 

332 >>> Line.are_concurrent(l2, l3, l4) 

333 False 

334 >>> from sympy import Point3D, Line3D 

335 >>> p1, p2 = Point3D(0, 0, 0), Point3D(3, 5, 2) 

336 >>> p3, p4 = Point3D(-2, -2, -2), Point3D(0, 2, 1) 

337 >>> l1, l2, l3 = Line3D(p1, p2), Line3D(p1, p3), Line3D(p1, p4) 

338 >>> Line3D.are_concurrent(l1, l2, l3) 

339 True 

340 >>> l4 = Line3D(p2, p3) 

341 >>> Line3D.are_concurrent(l2, l3, l4) 

342 False 

343 

344 """ 

345 common_points = Intersection(*lines) 

346 if common_points.is_FiniteSet and len(common_points) == 1: 

347 return True 

348 return False 

349 

350 def contains(self, other): 

351 """Subclasses should implement this method and should return 

352 True if other is on the boundaries of self; 

353 False if not on the boundaries of self; 

354 None if a determination cannot be made.""" 

355 raise NotImplementedError() 

356 

357 @property 

358 def direction(self): 

359 """The direction vector of the LinearEntity. 

360 

361 Returns 

362 ======= 

363 

364 p : a Point; the ray from the origin to this point is the 

365 direction of `self` 

366 

367 Examples 

368 ======== 

369 

370 >>> from sympy import Line 

371 >>> a, b = (1, 1), (1, 3) 

372 >>> Line(a, b).direction 

373 Point2D(0, 2) 

374 >>> Line(b, a).direction 

375 Point2D(0, -2) 

376 

377 This can be reported so the distance from the origin is 1: 

378 

379 >>> Line(b, a).direction.unit 

380 Point2D(0, -1) 

381 

382 See Also 

383 ======== 

384 

385 sympy.geometry.point.Point.unit 

386 

387 """ 

388 return self.p2 - self.p1 

389 

390 def intersection(self, other): 

391 """The intersection with another geometrical entity. 

392 

393 Parameters 

394 ========== 

395 

396 o : Point or LinearEntity 

397 

398 Returns 

399 ======= 

400 

401 intersection : list of geometrical entities 

402 

403 See Also 

404 ======== 

405 

406 sympy.geometry.point.Point 

407 

408 Examples 

409 ======== 

410 

411 >>> from sympy import Point, Line, Segment 

412 >>> p1, p2, p3 = Point(0, 0), Point(1, 1), Point(7, 7) 

413 >>> l1 = Line(p1, p2) 

414 >>> l1.intersection(p3) 

415 [Point2D(7, 7)] 

416 >>> p4, p5 = Point(5, 0), Point(0, 3) 

417 >>> l2 = Line(p4, p5) 

418 >>> l1.intersection(l2) 

419 [Point2D(15/8, 15/8)] 

420 >>> p6, p7 = Point(0, 5), Point(2, 6) 

421 >>> s1 = Segment(p6, p7) 

422 >>> l1.intersection(s1) 

423 [] 

424 >>> from sympy import Point3D, Line3D, Segment3D 

425 >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(1, 1, 1), Point3D(7, 7, 7) 

426 >>> l1 = Line3D(p1, p2) 

427 >>> l1.intersection(p3) 

428 [Point3D(7, 7, 7)] 

429 >>> l1 = Line3D(Point3D(4,19,12), Point3D(5,25,17)) 

430 >>> l2 = Line3D(Point3D(-3, -15, -19), direction_ratio=[2,8,8]) 

431 >>> l1.intersection(l2) 

432 [Point3D(1, 1, -3)] 

433 >>> p6, p7 = Point3D(0, 5, 2), Point3D(2, 6, 3) 

434 >>> s1 = Segment3D(p6, p7) 

435 >>> l1.intersection(s1) 

436 [] 

437 

438 """ 

439 def intersect_parallel_rays(ray1, ray2): 

440 if ray1.direction.dot(ray2.direction) > 0: 

441 # rays point in the same direction 

442 # so return the one that is "in front" 

443 return [ray2] if ray1._span_test(ray2.p1) >= 0 else [ray1] 

444 else: 

445 # rays point in opposite directions 

446 st = ray1._span_test(ray2.p1) 

447 if st < 0: 

448 return [] 

449 elif st == 0: 

450 return [ray2.p1] 

451 return [Segment(ray1.p1, ray2.p1)] 

452 

453 def intersect_parallel_ray_and_segment(ray, seg): 

454 st1, st2 = ray._span_test(seg.p1), ray._span_test(seg.p2) 

455 if st1 < 0 and st2 < 0: 

456 return [] 

457 elif st1 >= 0 and st2 >= 0: 

458 return [seg] 

459 elif st1 >= 0: # st2 < 0: 

460 return [Segment(ray.p1, seg.p1)] 

461 else: # st1 < 0 and st2 >= 0: 

462 return [Segment(ray.p1, seg.p2)] 

463 

464 def intersect_parallel_segments(seg1, seg2): 

465 if seg1.contains(seg2): 

466 return [seg2] 

467 if seg2.contains(seg1): 

468 return [seg1] 

469 

470 # direct the segments so they're oriented the same way 

471 if seg1.direction.dot(seg2.direction) < 0: 

472 seg2 = Segment(seg2.p2, seg2.p1) 

473 # order the segments so seg1 is "behind" seg2 

474 if seg1._span_test(seg2.p1) < 0: 

475 seg1, seg2 = seg2, seg1 

476 if seg2._span_test(seg1.p2) < 0: 

477 return [] 

478 return [Segment(seg2.p1, seg1.p2)] 

479 

480 if not isinstance(other, GeometryEntity): 

481 other = Point(other, dim=self.ambient_dimension) 

482 if other.is_Point: 

483 if self.contains(other): 

484 return [other] 

485 else: 

486 return [] 

487 elif isinstance(other, LinearEntity): 

488 # break into cases based on whether 

489 # the lines are parallel, non-parallel intersecting, or skew 

490 pts = Point._normalize_dimension(self.p1, self.p2, other.p1, other.p2) 

491 rank = Point.affine_rank(*pts) 

492 

493 if rank == 1: 

494 # we're collinear 

495 if isinstance(self, Line): 

496 return [other] 

497 if isinstance(other, Line): 

498 return [self] 

499 

500 if isinstance(self, Ray) and isinstance(other, Ray): 

501 return intersect_parallel_rays(self, other) 

502 if isinstance(self, Ray) and isinstance(other, Segment): 

503 return intersect_parallel_ray_and_segment(self, other) 

504 if isinstance(self, Segment) and isinstance(other, Ray): 

505 return intersect_parallel_ray_and_segment(other, self) 

506 if isinstance(self, Segment) and isinstance(other, Segment): 

507 return intersect_parallel_segments(self, other) 

508 elif rank == 2: 

509 # we're in the same plane 

510 l1 = Line(*pts[:2]) 

511 l2 = Line(*pts[2:]) 

512 

513 # check to see if we're parallel. If we are, we can't 

514 # be intersecting, since the collinear case was already 

515 # handled 

516 if l1.direction.is_scalar_multiple(l2.direction): 

517 return [] 

518 

519 # find the intersection as if everything were lines 

520 # by solving the equation t*d + p1 == s*d' + p1' 

521 m = Matrix([l1.direction, -l2.direction]).transpose() 

522 v = Matrix([l2.p1 - l1.p1]).transpose() 

523 

524 # we cannot use m.solve(v) because that only works for square matrices 

525 m_rref, pivots = m.col_insert(2, v).rref(simplify=True) 

526 # rank == 2 ensures we have 2 pivots, but let's check anyway 

527 if len(pivots) != 2: 

528 raise GeometryError("Failed when solving Mx=b when M={} and b={}".format(m, v)) 

529 coeff = m_rref[0, 2] 

530 line_intersection = l1.direction*coeff + self.p1 

531 

532 # if both are lines, skip a containment check 

533 if isinstance(self, Line) and isinstance(other, Line): 

534 return [line_intersection] 

535 

536 if ((isinstance(self, Line) or 

537 self.contains(line_intersection)) and 

538 other.contains(line_intersection)): 

539 return [line_intersection] 

540 if not self.atoms(Float) and not other.atoms(Float): 

541 # if it can fail when there are no Floats then 

542 # maybe the following parametric check should be 

543 # done 

544 return [] 

545 # floats may fail exact containment so check that the 

546 # arbitrary points, when equal, both give a 

547 # non-negative parameter when the arbitrary point 

548 # coordinates are equated 

549 tu = solve(self.arbitrary_point(t) - other.arbitrary_point(u), 

550 t, u, dict=True)[0] 

551 def ok(p, l): 

552 if isinstance(l, Line): 

553 # p > -oo 

554 return True 

555 if isinstance(l, Ray): 

556 # p >= 0 

557 return p.is_nonnegative 

558 if isinstance(l, Segment): 

559 # 0 <= p <= 1 

560 return p.is_nonnegative and (1 - p).is_nonnegative 

561 raise ValueError("unexpected line type") 

562 if ok(tu[t], self) and ok(tu[u], other): 

563 return [line_intersection] 

564 return [] 

565 else: 

566 # we're skew 

567 return [] 

568 

569 return other.intersection(self) 

570 

571 def is_parallel(l1, l2): 

572 """Are two linear entities parallel? 

573 

574 Parameters 

575 ========== 

576 

577 l1 : LinearEntity 

578 l2 : LinearEntity 

579 

580 Returns 

581 ======= 

582 

583 True : if l1 and l2 are parallel, 

584 False : otherwise. 

585 

586 See Also 

587 ======== 

588 

589 coefficients 

590 

591 Examples 

592 ======== 

593 

594 >>> from sympy import Point, Line 

595 >>> p1, p2 = Point(0, 0), Point(1, 1) 

596 >>> p3, p4 = Point(3, 4), Point(6, 7) 

597 >>> l1, l2 = Line(p1, p2), Line(p3, p4) 

598 >>> Line.is_parallel(l1, l2) 

599 True 

600 >>> p5 = Point(6, 6) 

601 >>> l3 = Line(p3, p5) 

602 >>> Line.is_parallel(l1, l3) 

603 False 

604 >>> from sympy import Point3D, Line3D 

605 >>> p1, p2 = Point3D(0, 0, 0), Point3D(3, 4, 5) 

606 >>> p3, p4 = Point3D(2, 1, 1), Point3D(8, 9, 11) 

607 >>> l1, l2 = Line3D(p1, p2), Line3D(p3, p4) 

608 >>> Line3D.is_parallel(l1, l2) 

609 True 

610 >>> p5 = Point3D(6, 6, 6) 

611 >>> l3 = Line3D(p3, p5) 

612 >>> Line3D.is_parallel(l1, l3) 

613 False 

614 

615 """ 

616 if not isinstance(l1, LinearEntity) and not isinstance(l2, LinearEntity): 

617 raise TypeError('Must pass only LinearEntity objects') 

618 

619 return l1.direction.is_scalar_multiple(l2.direction) 

620 

621 def is_perpendicular(l1, l2): 

622 """Are two linear entities perpendicular? 

623 

624 Parameters 

625 ========== 

626 

627 l1 : LinearEntity 

628 l2 : LinearEntity 

629 

630 Returns 

631 ======= 

632 

633 True : if l1 and l2 are perpendicular, 

634 False : otherwise. 

635 

636 See Also 

637 ======== 

638 

639 coefficients 

640 

641 Examples 

642 ======== 

643 

644 >>> from sympy import Point, Line 

645 >>> p1, p2, p3 = Point(0, 0), Point(1, 1), Point(-1, 1) 

646 >>> l1, l2 = Line(p1, p2), Line(p1, p3) 

647 >>> l1.is_perpendicular(l2) 

648 True 

649 >>> p4 = Point(5, 3) 

650 >>> l3 = Line(p1, p4) 

651 >>> l1.is_perpendicular(l3) 

652 False 

653 >>> from sympy import Point3D, Line3D 

654 >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(1, 1, 1), Point3D(-1, 2, 0) 

655 >>> l1, l2 = Line3D(p1, p2), Line3D(p2, p3) 

656 >>> l1.is_perpendicular(l2) 

657 False 

658 >>> p4 = Point3D(5, 3, 7) 

659 >>> l3 = Line3D(p1, p4) 

660 >>> l1.is_perpendicular(l3) 

661 False 

662 

663 """ 

664 if not isinstance(l1, LinearEntity) and not isinstance(l2, LinearEntity): 

665 raise TypeError('Must pass only LinearEntity objects') 

666 

667 return S.Zero.equals(l1.direction.dot(l2.direction)) 

668 

669 def is_similar(self, other): 

670 """ 

671 Return True if self and other are contained in the same line. 

672 

673 Examples 

674 ======== 

675 

676 >>> from sympy import Point, Line 

677 >>> p1, p2, p3 = Point(0, 1), Point(3, 4), Point(2, 3) 

678 >>> l1 = Line(p1, p2) 

679 >>> l2 = Line(p1, p3) 

680 >>> l1.is_similar(l2) 

681 True 

682 """ 

683 l = Line(self.p1, self.p2) 

684 return l.contains(other) 

685 

686 @property 

687 def length(self): 

688 """ 

689 The length of the line. 

690 

691 Examples 

692 ======== 

693 

694 >>> from sympy import Point, Line 

695 >>> p1, p2 = Point(0, 0), Point(3, 5) 

696 >>> l1 = Line(p1, p2) 

697 >>> l1.length 

698 oo 

699 """ 

700 return S.Infinity 

701 

702 @property 

703 def p1(self): 

704 """The first defining point of a linear entity. 

705 

706 See Also 

707 ======== 

708 

709 sympy.geometry.point.Point 

710 

711 Examples 

712 ======== 

713 

714 >>> from sympy import Point, Line 

715 >>> p1, p2 = Point(0, 0), Point(5, 3) 

716 >>> l = Line(p1, p2) 

717 >>> l.p1 

718 Point2D(0, 0) 

719 

720 """ 

721 return self.args[0] 

722 

723 @property 

724 def p2(self): 

725 """The second defining point of a linear entity. 

726 

727 See Also 

728 ======== 

729 

730 sympy.geometry.point.Point 

731 

732 Examples 

733 ======== 

734 

735 >>> from sympy import Point, Line 

736 >>> p1, p2 = Point(0, 0), Point(5, 3) 

737 >>> l = Line(p1, p2) 

738 >>> l.p2 

739 Point2D(5, 3) 

740 

741 """ 

742 return self.args[1] 

743 

744 def parallel_line(self, p): 

745 """Create a new Line parallel to this linear entity which passes 

746 through the point `p`. 

747 

748 Parameters 

749 ========== 

750 

751 p : Point 

752 

753 Returns 

754 ======= 

755 

756 line : Line 

757 

758 See Also 

759 ======== 

760 

761 is_parallel 

762 

763 Examples 

764 ======== 

765 

766 >>> from sympy import Point, Line 

767 >>> p1, p2, p3 = Point(0, 0), Point(2, 3), Point(-2, 2) 

768 >>> l1 = Line(p1, p2) 

769 >>> l2 = l1.parallel_line(p3) 

770 >>> p3 in l2 

771 True 

772 >>> l1.is_parallel(l2) 

773 True 

774 >>> from sympy import Point3D, Line3D 

775 >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(2, 3, 4), Point3D(-2, 2, 0) 

776 >>> l1 = Line3D(p1, p2) 

777 >>> l2 = l1.parallel_line(p3) 

778 >>> p3 in l2 

779 True 

780 >>> l1.is_parallel(l2) 

781 True 

782 

783 """ 

784 p = Point(p, dim=self.ambient_dimension) 

785 return Line(p, p + self.direction) 

786 

787 def perpendicular_line(self, p): 

788 """Create a new Line perpendicular to this linear entity which passes 

789 through the point `p`. 

790 

791 Parameters 

792 ========== 

793 

794 p : Point 

795 

796 Returns 

797 ======= 

798 

799 line : Line 

800 

801 See Also 

802 ======== 

803 

804 sympy.geometry.line.LinearEntity.is_perpendicular, perpendicular_segment 

805 

806 Examples 

807 ======== 

808 

809 >>> from sympy import Point3D, Line3D 

810 >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(2, 3, 4), Point3D(-2, 2, 0) 

811 >>> L = Line3D(p1, p2) 

812 >>> P = L.perpendicular_line(p3); P 

813 Line3D(Point3D(-2, 2, 0), Point3D(4/29, 6/29, 8/29)) 

814 >>> L.is_perpendicular(P) 

815 True 

816 

817 In 3D the, the first point used to define the line is the point 

818 through which the perpendicular was required to pass; the 

819 second point is (arbitrarily) contained in the given line: 

820 

821 >>> P.p2 in L 

822 True 

823 """ 

824 p = Point(p, dim=self.ambient_dimension) 

825 if p in self: 

826 p = p + self.direction.orthogonal_direction 

827 return Line(p, self.projection(p)) 

828 

829 def perpendicular_segment(self, p): 

830 """Create a perpendicular line segment from `p` to this line. 

831 

832 The endpoints of the segment are ``p`` and the closest point in 

833 the line containing self. (If self is not a line, the point might 

834 not be in self.) 

835 

836 Parameters 

837 ========== 

838 

839 p : Point 

840 

841 Returns 

842 ======= 

843 

844 segment : Segment 

845 

846 Notes 

847 ===== 

848 

849 Returns `p` itself if `p` is on this linear entity. 

850 

851 See Also 

852 ======== 

853 

854 perpendicular_line 

855 

856 Examples 

857 ======== 

858 

859 >>> from sympy import Point, Line 

860 >>> p1, p2, p3 = Point(0, 0), Point(1, 1), Point(0, 2) 

861 >>> l1 = Line(p1, p2) 

862 >>> s1 = l1.perpendicular_segment(p3) 

863 >>> l1.is_perpendicular(s1) 

864 True 

865 >>> p3 in s1 

866 True 

867 >>> l1.perpendicular_segment(Point(4, 0)) 

868 Segment2D(Point2D(4, 0), Point2D(2, 2)) 

869 >>> from sympy import Point3D, Line3D 

870 >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(1, 1, 1), Point3D(0, 2, 0) 

871 >>> l1 = Line3D(p1, p2) 

872 >>> s1 = l1.perpendicular_segment(p3) 

873 >>> l1.is_perpendicular(s1) 

874 True 

875 >>> p3 in s1 

876 True 

877 >>> l1.perpendicular_segment(Point3D(4, 0, 0)) 

878 Segment3D(Point3D(4, 0, 0), Point3D(4/3, 4/3, 4/3)) 

879 

880 """ 

881 p = Point(p, dim=self.ambient_dimension) 

882 if p in self: 

883 return p 

884 l = self.perpendicular_line(p) 

885 # The intersection should be unique, so unpack the singleton 

886 p2, = Intersection(Line(self.p1, self.p2), l) 

887 

888 return Segment(p, p2) 

889 

890 @property 

891 def points(self): 

892 """The two points used to define this linear entity. 

893 

894 Returns 

895 ======= 

896 

897 points : tuple of Points 

898 

899 See Also 

900 ======== 

901 

902 sympy.geometry.point.Point 

903 

904 Examples 

905 ======== 

906 

907 >>> from sympy import Point, Line 

908 >>> p1, p2 = Point(0, 0), Point(5, 11) 

909 >>> l1 = Line(p1, p2) 

910 >>> l1.points 

911 (Point2D(0, 0), Point2D(5, 11)) 

912 

913 """ 

914 return (self.p1, self.p2) 

915 

916 def projection(self, other): 

917 """Project a point, line, ray, or segment onto this linear entity. 

918 

919 Parameters 

920 ========== 

921 

922 other : Point or LinearEntity (Line, Ray, Segment) 

923 

924 Returns 

925 ======= 

926 

927 projection : Point or LinearEntity (Line, Ray, Segment) 

928 The return type matches the type of the parameter ``other``. 

929 

930 Raises 

931 ====== 

932 

933 GeometryError 

934 When method is unable to perform projection. 

935 

936 Notes 

937 ===== 

938 

939 A projection involves taking the two points that define 

940 the linear entity and projecting those points onto a 

941 Line and then reforming the linear entity using these 

942 projections. 

943 A point P is projected onto a line L by finding the point 

944 on L that is closest to P. This point is the intersection 

945 of L and the line perpendicular to L that passes through P. 

946 

947 See Also 

948 ======== 

949 

950 sympy.geometry.point.Point, perpendicular_line 

951 

952 Examples 

953 ======== 

954 

955 >>> from sympy import Point, Line, Segment, Rational 

956 >>> p1, p2, p3 = Point(0, 0), Point(1, 1), Point(Rational(1, 2), 0) 

957 >>> l1 = Line(p1, p2) 

958 >>> l1.projection(p3) 

959 Point2D(1/4, 1/4) 

960 >>> p4, p5 = Point(10, 0), Point(12, 1) 

961 >>> s1 = Segment(p4, p5) 

962 >>> l1.projection(s1) 

963 Segment2D(Point2D(5, 5), Point2D(13/2, 13/2)) 

964 >>> p1, p2, p3 = Point(0, 0, 1), Point(1, 1, 2), Point(2, 0, 1) 

965 >>> l1 = Line(p1, p2) 

966 >>> l1.projection(p3) 

967 Point3D(2/3, 2/3, 5/3) 

968 >>> p4, p5 = Point(10, 0, 1), Point(12, 1, 3) 

969 >>> s1 = Segment(p4, p5) 

970 >>> l1.projection(s1) 

971 Segment3D(Point3D(10/3, 10/3, 13/3), Point3D(5, 5, 6)) 

972 

973 """ 

974 if not isinstance(other, GeometryEntity): 

975 other = Point(other, dim=self.ambient_dimension) 

976 

977 def proj_point(p): 

978 return Point.project(p - self.p1, self.direction) + self.p1 

979 

980 if isinstance(other, Point): 

981 return proj_point(other) 

982 elif isinstance(other, LinearEntity): 

983 p1, p2 = proj_point(other.p1), proj_point(other.p2) 

984 # test to see if we're degenerate 

985 if p1 == p2: 

986 return p1 

987 projected = other.__class__(p1, p2) 

988 projected = Intersection(self, projected) 

989 if projected.is_empty: 

990 return projected 

991 # if we happen to have intersected in only a point, return that 

992 if projected.is_FiniteSet and len(projected) == 1: 

993 # projected is a set of size 1, so unpack it in `a` 

994 a, = projected 

995 return a 

996 # order args so projection is in the same direction as self 

997 if self.direction.dot(projected.direction) < 0: 

998 p1, p2 = projected.args 

999 projected = projected.func(p2, p1) 

1000 return projected 

1001 

1002 raise GeometryError( 

1003 "Do not know how to project %s onto %s" % (other, self)) 

1004 

1005 def random_point(self, seed=None): 

1006 """A random point on a LinearEntity. 

1007 

1008 Returns 

1009 ======= 

1010 

1011 point : Point 

1012 

1013 See Also 

1014 ======== 

1015 

1016 sympy.geometry.point.Point 

1017 

1018 Examples 

1019 ======== 

1020 

1021 >>> from sympy import Point, Line, Ray, Segment 

1022 >>> p1, p2 = Point(0, 0), Point(5, 3) 

1023 >>> line = Line(p1, p2) 

1024 >>> r = line.random_point(seed=42) # seed value is optional 

1025 >>> r.n(3) 

1026 Point2D(-0.72, -0.432) 

1027 >>> r in line 

1028 True 

1029 >>> Ray(p1, p2).random_point(seed=42).n(3) 

1030 Point2D(0.72, 0.432) 

1031 >>> Segment(p1, p2).random_point(seed=42).n(3) 

1032 Point2D(3.2, 1.92) 

1033 

1034 """ 

1035 if seed is not None: 

1036 rng = random.Random(seed) 

1037 else: 

1038 rng = random 

1039 pt = self.arbitrary_point(t) 

1040 if isinstance(self, Ray): 

1041 v = abs(rng.gauss(0, 1)) 

1042 elif isinstance(self, Segment): 

1043 v = rng.random() 

1044 elif isinstance(self, Line): 

1045 v = rng.gauss(0, 1) 

1046 else: 

1047 raise NotImplementedError('unhandled line type') 

1048 return pt.subs(t, Rational(v)) 

1049 

1050 def bisectors(self, other): 

1051 """Returns the perpendicular lines which pass through the intersections 

1052 of self and other that are in the same plane. 

1053 

1054 Parameters 

1055 ========== 

1056 

1057 line : Line3D 

1058 

1059 Returns 

1060 ======= 

1061 

1062 list: two Line instances 

1063 

1064 Examples 

1065 ======== 

1066 

1067 >>> from sympy import Point3D, Line3D 

1068 >>> r1 = Line3D(Point3D(0, 0, 0), Point3D(1, 0, 0)) 

1069 >>> r2 = Line3D(Point3D(0, 0, 0), Point3D(0, 1, 0)) 

1070 >>> r1.bisectors(r2) 

1071 [Line3D(Point3D(0, 0, 0), Point3D(1, 1, 0)), Line3D(Point3D(0, 0, 0), Point3D(1, -1, 0))] 

1072 

1073 """ 

1074 if not isinstance(other, LinearEntity): 

1075 raise GeometryError("Expecting LinearEntity, not %s" % other) 

1076 

1077 l1, l2 = self, other 

1078 

1079 # make sure dimensions match or else a warning will rise from 

1080 # intersection calculation 

1081 if l1.p1.ambient_dimension != l2.p1.ambient_dimension: 

1082 if isinstance(l1, Line2D): 

1083 l1, l2 = l2, l1 

1084 _, p1 = Point._normalize_dimension(l1.p1, l2.p1, on_morph='ignore') 

1085 _, p2 = Point._normalize_dimension(l1.p2, l2.p2, on_morph='ignore') 

1086 l2 = Line(p1, p2) 

1087 

1088 point = intersection(l1, l2) 

1089 

1090 # Three cases: Lines may intersect in a point, may be equal or may not intersect. 

1091 if not point: 

1092 raise GeometryError("The lines do not intersect") 

1093 else: 

1094 pt = point[0] 

1095 if isinstance(pt, Line): 

1096 # Intersection is a line because both lines are coincident 

1097 return [self] 

1098 

1099 

1100 d1 = l1.direction.unit 

1101 d2 = l2.direction.unit 

1102 

1103 bis1 = Line(pt, pt + d1 + d2) 

1104 bis2 = Line(pt, pt + d1 - d2) 

1105 

1106 return [bis1, bis2] 

1107 

1108 

1109class Line(LinearEntity): 

1110 """An infinite line in space. 

1111 

1112 A 2D line is declared with two distinct points, point and slope, or 

1113 an equation. A 3D line may be defined with a point and a direction ratio. 

1114 

1115 Parameters 

1116 ========== 

1117 

1118 p1 : Point 

1119 p2 : Point 

1120 slope : SymPy expression 

1121 direction_ratio : list 

1122 equation : equation of a line 

1123 

1124 Notes 

1125 ===== 

1126 

1127 `Line` will automatically subclass to `Line2D` or `Line3D` based 

1128 on the dimension of `p1`. The `slope` argument is only relevant 

1129 for `Line2D` and the `direction_ratio` argument is only relevant 

1130 for `Line3D`. 

1131 

1132 The order of the points will define the direction of the line 

1133 which is used when calculating the angle between lines. 

1134 

1135 See Also 

1136 ======== 

1137 

1138 sympy.geometry.point.Point 

1139 sympy.geometry.line.Line2D 

1140 sympy.geometry.line.Line3D 

1141 

1142 Examples 

1143 ======== 

1144 

1145 >>> from sympy import Line, Segment, Point, Eq 

1146 >>> from sympy.abc import x, y, a, b 

1147 

1148 >>> L = Line(Point(2,3), Point(3,5)) 

1149 >>> L 

1150 Line2D(Point2D(2, 3), Point2D(3, 5)) 

1151 >>> L.points 

1152 (Point2D(2, 3), Point2D(3, 5)) 

1153 >>> L.equation() 

1154 -2*x + y + 1 

1155 >>> L.coefficients 

1156 (-2, 1, 1) 

1157 

1158 Instantiate with keyword ``slope``: 

1159 

1160 >>> Line(Point(0, 0), slope=0) 

1161 Line2D(Point2D(0, 0), Point2D(1, 0)) 

1162 

1163 Instantiate with another linear object 

1164 

1165 >>> s = Segment((0, 0), (0, 1)) 

1166 >>> Line(s).equation() 

1167 x 

1168 

1169 The line corresponding to an equation in the for `ax + by + c = 0`, 

1170 can be entered: 

1171 

1172 >>> Line(3*x + y + 18) 

1173 Line2D(Point2D(0, -18), Point2D(1, -21)) 

1174 

1175 If `x` or `y` has a different name, then they can be specified, too, 

1176 as a string (to match the name) or symbol: 

1177 

1178 >>> Line(Eq(3*a + b, -18), x='a', y=b) 

1179 Line2D(Point2D(0, -18), Point2D(1, -21)) 

1180 """ 

1181 def __new__(cls, *args, **kwargs): 

1182 if len(args) == 1 and isinstance(args[0], (Expr, Eq)): 

1183 missing = uniquely_named_symbol('?', args) 

1184 if not kwargs: 

1185 x = 'x' 

1186 y = 'y' 

1187 else: 

1188 x = kwargs.pop('x', missing) 

1189 y = kwargs.pop('y', missing) 

1190 if kwargs: 

1191 raise ValueError('expecting only x and y as keywords') 

1192 

1193 equation = args[0] 

1194 if isinstance(equation, Eq): 

1195 equation = equation.lhs - equation.rhs 

1196 

1197 def find_or_missing(x): 

1198 try: 

1199 return find(x, equation) 

1200 except ValueError: 

1201 return missing 

1202 x = find_or_missing(x) 

1203 y = find_or_missing(y) 

1204 

1205 a, b, c = linear_coeffs(equation, x, y) 

1206 

1207 if b: 

1208 return Line((0, -c/b), slope=-a/b) 

1209 if a: 

1210 return Line((-c/a, 0), slope=oo) 

1211 

1212 raise ValueError('not found in equation: %s' % (set('xy') - {x, y})) 

1213 

1214 else: 

1215 if len(args) > 0: 

1216 p1 = args[0] 

1217 if len(args) > 1: 

1218 p2 = args[1] 

1219 else: 

1220 p2 = None 

1221 

1222 if isinstance(p1, LinearEntity): 

1223 if p2: 

1224 raise ValueError('If p1 is a LinearEntity, p2 must be None.') 

1225 dim = len(p1.p1) 

1226 else: 

1227 p1 = Point(p1) 

1228 dim = len(p1) 

1229 if p2 is not None or isinstance(p2, Point) and p2.ambient_dimension != dim: 

1230 p2 = Point(p2) 

1231 

1232 if dim == 2: 

1233 return Line2D(p1, p2, **kwargs) 

1234 elif dim == 3: 

1235 return Line3D(p1, p2, **kwargs) 

1236 return LinearEntity.__new__(cls, p1, p2, **kwargs) 

1237 

1238 def contains(self, other): 

1239 """ 

1240 Return True if `other` is on this Line, or False otherwise. 

1241 

1242 Examples 

1243 ======== 

1244 

1245 >>> from sympy import Line,Point 

1246 >>> p1, p2 = Point(0, 1), Point(3, 4) 

1247 >>> l = Line(p1, p2) 

1248 >>> l.contains(p1) 

1249 True 

1250 >>> l.contains((0, 1)) 

1251 True 

1252 >>> l.contains((0, 0)) 

1253 False 

1254 >>> a = (0, 0, 0) 

1255 >>> b = (1, 1, 1) 

1256 >>> c = (2, 2, 2) 

1257 >>> l1 = Line(a, b) 

1258 >>> l2 = Line(b, a) 

1259 >>> l1 == l2 

1260 False 

1261 >>> l1 in l2 

1262 True 

1263 

1264 """ 

1265 if not isinstance(other, GeometryEntity): 

1266 other = Point(other, dim=self.ambient_dimension) 

1267 if isinstance(other, Point): 

1268 return Point.is_collinear(other, self.p1, self.p2) 

1269 if isinstance(other, LinearEntity): 

1270 return Point.is_collinear(self.p1, self.p2, other.p1, other.p2) 

1271 return False 

1272 

1273 def distance(self, other): 

1274 """ 

1275 Finds the shortest distance between a line and a point. 

1276 

1277 Raises 

1278 ====== 

1279 

1280 NotImplementedError is raised if `other` is not a Point 

1281 

1282 Examples 

1283 ======== 

1284 

1285 >>> from sympy import Point, Line 

1286 >>> p1, p2 = Point(0, 0), Point(1, 1) 

1287 >>> s = Line(p1, p2) 

1288 >>> s.distance(Point(-1, 1)) 

1289 sqrt(2) 

1290 >>> s.distance((-1, 2)) 

1291 3*sqrt(2)/2 

1292 >>> p1, p2 = Point(0, 0, 0), Point(1, 1, 1) 

1293 >>> s = Line(p1, p2) 

1294 >>> s.distance(Point(-1, 1, 1)) 

1295 2*sqrt(6)/3 

1296 >>> s.distance((-1, 1, 1)) 

1297 2*sqrt(6)/3 

1298 

1299 """ 

1300 if not isinstance(other, GeometryEntity): 

1301 other = Point(other, dim=self.ambient_dimension) 

1302 if self.contains(other): 

1303 return S.Zero 

1304 return self.perpendicular_segment(other).length 

1305 

1306 def equals(self, other): 

1307 """Returns True if self and other are the same mathematical entities""" 

1308 if not isinstance(other, Line): 

1309 return False 

1310 return Point.is_collinear(self.p1, other.p1, self.p2, other.p2) 

1311 

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

1313 """The plot interval for the default geometric plot of line. Gives 

1314 values that will produce a line that is +/- 5 units long (where a 

1315 unit is the distance between the two points that define the line). 

1316 

1317 Parameters 

1318 ========== 

1319 

1320 parameter : str, optional 

1321 Default value is 't'. 

1322 

1323 Returns 

1324 ======= 

1325 

1326 plot_interval : list (plot interval) 

1327 [parameter, lower_bound, upper_bound] 

1328 

1329 Examples 

1330 ======== 

1331 

1332 >>> from sympy import Point, Line 

1333 >>> p1, p2 = Point(0, 0), Point(5, 3) 

1334 >>> l1 = Line(p1, p2) 

1335 >>> l1.plot_interval() 

1336 [t, -5, 5] 

1337 

1338 """ 

1339 t = _symbol(parameter, real=True) 

1340 return [t, -5, 5] 

1341 

1342 

1343class Ray(LinearEntity): 

1344 """A Ray is a semi-line in the space with a source point and a direction. 

1345 

1346 Parameters 

1347 ========== 

1348 

1349 p1 : Point 

1350 The source of the Ray 

1351 p2 : Point or radian value 

1352 This point determines the direction in which the Ray propagates. 

1353 If given as an angle it is interpreted in radians with the positive 

1354 direction being ccw. 

1355 

1356 Attributes 

1357 ========== 

1358 

1359 source 

1360 

1361 See Also 

1362 ======== 

1363 

1364 sympy.geometry.line.Ray2D 

1365 sympy.geometry.line.Ray3D 

1366 sympy.geometry.point.Point 

1367 sympy.geometry.line.Line 

1368 

1369 Notes 

1370 ===== 

1371 

1372 `Ray` will automatically subclass to `Ray2D` or `Ray3D` based on the 

1373 dimension of `p1`. 

1374 

1375 Examples 

1376 ======== 

1377 

1378 >>> from sympy import Ray, Point, pi 

1379 >>> r = Ray(Point(2, 3), Point(3, 5)) 

1380 >>> r 

1381 Ray2D(Point2D(2, 3), Point2D(3, 5)) 

1382 >>> r.points 

1383 (Point2D(2, 3), Point2D(3, 5)) 

1384 >>> r.source 

1385 Point2D(2, 3) 

1386 >>> r.xdirection 

1387 oo 

1388 >>> r.ydirection 

1389 oo 

1390 >>> r.slope 

1391 2 

1392 >>> Ray(Point(0, 0), angle=pi/4).slope 

1393 1 

1394 

1395 """ 

1396 def __new__(cls, p1, p2=None, **kwargs): 

1397 p1 = Point(p1) 

1398 if p2 is not None: 

1399 p1, p2 = Point._normalize_dimension(p1, Point(p2)) 

1400 dim = len(p1) 

1401 

1402 if dim == 2: 

1403 return Ray2D(p1, p2, **kwargs) 

1404 elif dim == 3: 

1405 return Ray3D(p1, p2, **kwargs) 

1406 return LinearEntity.__new__(cls, p1, p2, **kwargs) 

1407 

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

1409 """Returns SVG path element for the LinearEntity. 

1410 

1411 Parameters 

1412 ========== 

1413 

1414 scale_factor : float 

1415 Multiplication factor for the SVG stroke-width. Default is 1. 

1416 fill_color : str, optional 

1417 Hex string for fill color. Default is "#66cc99". 

1418 """ 

1419 verts = (N(self.p1), N(self.p2)) 

1420 coords = ["{},{}".format(p.x, p.y) for p in verts] 

1421 path = "M {} L {}".format(coords[0], " L ".join(coords[1:])) 

1422 

1423 return ( 

1424 '<path fill-rule="evenodd" fill="{2}" stroke="#555555" ' 

1425 'stroke-width="{0}" opacity="0.6" d="{1}" ' 

1426 'marker-start="url(#markerCircle)" marker-end="url(#markerArrow)"/>' 

1427 ).format(2.*scale_factor, path, fill_color) 

1428 

1429 def contains(self, other): 

1430 """ 

1431 Is other GeometryEntity contained in this Ray? 

1432 

1433 Examples 

1434 ======== 

1435 

1436 >>> from sympy import Ray,Point,Segment 

1437 >>> p1, p2 = Point(0, 0), Point(4, 4) 

1438 >>> r = Ray(p1, p2) 

1439 >>> r.contains(p1) 

1440 True 

1441 >>> r.contains((1, 1)) 

1442 True 

1443 >>> r.contains((1, 3)) 

1444 False 

1445 >>> s = Segment((1, 1), (2, 2)) 

1446 >>> r.contains(s) 

1447 True 

1448 >>> s = Segment((1, 2), (2, 5)) 

1449 >>> r.contains(s) 

1450 False 

1451 >>> r1 = Ray((2, 2), (3, 3)) 

1452 >>> r.contains(r1) 

1453 True 

1454 >>> r1 = Ray((2, 2), (3, 5)) 

1455 >>> r.contains(r1) 

1456 False 

1457 """ 

1458 if not isinstance(other, GeometryEntity): 

1459 other = Point(other, dim=self.ambient_dimension) 

1460 if isinstance(other, Point): 

1461 if Point.is_collinear(self.p1, self.p2, other): 

1462 # if we're in the direction of the ray, our 

1463 # direction vector dot the ray's direction vector 

1464 # should be non-negative 

1465 return bool((self.p2 - self.p1).dot(other - self.p1) >= S.Zero) 

1466 return False 

1467 elif isinstance(other, Ray): 

1468 if Point.is_collinear(self.p1, self.p2, other.p1, other.p2): 

1469 return bool((self.p2 - self.p1).dot(other.p2 - other.p1) > S.Zero) 

1470 return False 

1471 elif isinstance(other, Segment): 

1472 return other.p1 in self and other.p2 in self 

1473 

1474 # No other known entity can be contained in a Ray 

1475 return False 

1476 

1477 def distance(self, other): 

1478 """ 

1479 Finds the shortest distance between the ray and a point. 

1480 

1481 Raises 

1482 ====== 

1483 

1484 NotImplementedError is raised if `other` is not a Point 

1485 

1486 Examples 

1487 ======== 

1488 

1489 >>> from sympy import Point, Ray 

1490 >>> p1, p2 = Point(0, 0), Point(1, 1) 

1491 >>> s = Ray(p1, p2) 

1492 >>> s.distance(Point(-1, -1)) 

1493 sqrt(2) 

1494 >>> s.distance((-1, 2)) 

1495 3*sqrt(2)/2 

1496 >>> p1, p2 = Point(0, 0, 0), Point(1, 1, 2) 

1497 >>> s = Ray(p1, p2) 

1498 >>> s 

1499 Ray3D(Point3D(0, 0, 0), Point3D(1, 1, 2)) 

1500 >>> s.distance(Point(-1, -1, 2)) 

1501 4*sqrt(3)/3 

1502 >>> s.distance((-1, -1, 2)) 

1503 4*sqrt(3)/3 

1504 

1505 """ 

1506 if not isinstance(other, GeometryEntity): 

1507 other = Point(other, dim=self.ambient_dimension) 

1508 if self.contains(other): 

1509 return S.Zero 

1510 

1511 proj = Line(self.p1, self.p2).projection(other) 

1512 if self.contains(proj): 

1513 return abs(other - proj) 

1514 else: 

1515 return abs(other - self.source) 

1516 

1517 def equals(self, other): 

1518 """Returns True if self and other are the same mathematical entities""" 

1519 if not isinstance(other, Ray): 

1520 return False 

1521 return self.source == other.source and other.p2 in self 

1522 

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

1524 """The plot interval for the default geometric plot of the Ray. Gives 

1525 values that will produce a ray that is 10 units long (where a unit is 

1526 the distance between the two points that define the ray). 

1527 

1528 Parameters 

1529 ========== 

1530 

1531 parameter : str, optional 

1532 Default value is 't'. 

1533 

1534 Returns 

1535 ======= 

1536 

1537 plot_interval : list 

1538 [parameter, lower_bound, upper_bound] 

1539 

1540 Examples 

1541 ======== 

1542 

1543 >>> from sympy import Ray, pi 

1544 >>> r = Ray((0, 0), angle=pi/4) 

1545 >>> r.plot_interval() 

1546 [t, 0, 10] 

1547 

1548 """ 

1549 t = _symbol(parameter, real=True) 

1550 return [t, 0, 10] 

1551 

1552 @property 

1553 def source(self): 

1554 """The point from which the ray emanates. 

1555 

1556 See Also 

1557 ======== 

1558 

1559 sympy.geometry.point.Point 

1560 

1561 Examples 

1562 ======== 

1563 

1564 >>> from sympy import Point, Ray 

1565 >>> p1, p2 = Point(0, 0), Point(4, 1) 

1566 >>> r1 = Ray(p1, p2) 

1567 >>> r1.source 

1568 Point2D(0, 0) 

1569 >>> p1, p2 = Point(0, 0, 0), Point(4, 1, 5) 

1570 >>> r1 = Ray(p2, p1) 

1571 >>> r1.source 

1572 Point3D(4, 1, 5) 

1573 

1574 """ 

1575 return self.p1 

1576 

1577 

1578class Segment(LinearEntity): 

1579 """A line segment in space. 

1580 

1581 Parameters 

1582 ========== 

1583 

1584 p1 : Point 

1585 p2 : Point 

1586 

1587 Attributes 

1588 ========== 

1589 

1590 length : number or SymPy expression 

1591 midpoint : Point 

1592 

1593 See Also 

1594 ======== 

1595 

1596 sympy.geometry.line.Segment2D 

1597 sympy.geometry.line.Segment3D 

1598 sympy.geometry.point.Point 

1599 sympy.geometry.line.Line 

1600 

1601 Notes 

1602 ===== 

1603 

1604 If 2D or 3D points are used to define `Segment`, it will 

1605 be automatically subclassed to `Segment2D` or `Segment3D`. 

1606 

1607 Examples 

1608 ======== 

1609 

1610 >>> from sympy import Point, Segment 

1611 >>> Segment((1, 0), (1, 1)) # tuples are interpreted as pts 

1612 Segment2D(Point2D(1, 0), Point2D(1, 1)) 

1613 >>> s = Segment(Point(4, 3), Point(1, 1)) 

1614 >>> s.points 

1615 (Point2D(4, 3), Point2D(1, 1)) 

1616 >>> s.slope 

1617 2/3 

1618 >>> s.length 

1619 sqrt(13) 

1620 >>> s.midpoint 

1621 Point2D(5/2, 2) 

1622 >>> Segment((1, 0, 0), (1, 1, 1)) # tuples are interpreted as pts 

1623 Segment3D(Point3D(1, 0, 0), Point3D(1, 1, 1)) 

1624 >>> s = Segment(Point(4, 3, 9), Point(1, 1, 7)); s 

1625 Segment3D(Point3D(4, 3, 9), Point3D(1, 1, 7)) 

1626 >>> s.points 

1627 (Point3D(4, 3, 9), Point3D(1, 1, 7)) 

1628 >>> s.length 

1629 sqrt(17) 

1630 >>> s.midpoint 

1631 Point3D(5/2, 2, 8) 

1632 

1633 """ 

1634 def __new__(cls, p1, p2, **kwargs): 

1635 p1, p2 = Point._normalize_dimension(Point(p1), Point(p2)) 

1636 dim = len(p1) 

1637 

1638 if dim == 2: 

1639 return Segment2D(p1, p2, **kwargs) 

1640 elif dim == 3: 

1641 return Segment3D(p1, p2, **kwargs) 

1642 return LinearEntity.__new__(cls, p1, p2, **kwargs) 

1643 

1644 def contains(self, other): 

1645 """ 

1646 Is the other GeometryEntity contained within this Segment? 

1647 

1648 Examples 

1649 ======== 

1650 

1651 >>> from sympy import Point, Segment 

1652 >>> p1, p2 = Point(0, 1), Point(3, 4) 

1653 >>> s = Segment(p1, p2) 

1654 >>> s2 = Segment(p2, p1) 

1655 >>> s.contains(s2) 

1656 True 

1657 >>> from sympy import Point3D, Segment3D 

1658 >>> p1, p2 = Point3D(0, 1, 1), Point3D(3, 4, 5) 

1659 >>> s = Segment3D(p1, p2) 

1660 >>> s2 = Segment3D(p2, p1) 

1661 >>> s.contains(s2) 

1662 True 

1663 >>> s.contains((p1 + p2)/2) 

1664 True 

1665 """ 

1666 if not isinstance(other, GeometryEntity): 

1667 other = Point(other, dim=self.ambient_dimension) 

1668 if isinstance(other, Point): 

1669 if Point.is_collinear(other, self.p1, self.p2): 

1670 if isinstance(self, Segment2D): 

1671 # if it is collinear and is in the bounding box of the 

1672 # segment then it must be on the segment 

1673 vert = (1/self.slope).equals(0) 

1674 if vert is False: 

1675 isin = (self.p1.x - other.x)*(self.p2.x - other.x) <= 0 

1676 if isin in (True, False): 

1677 return isin 

1678 if vert is True: 

1679 isin = (self.p1.y - other.y)*(self.p2.y - other.y) <= 0 

1680 if isin in (True, False): 

1681 return isin 

1682 # use the triangle inequality 

1683 d1, d2 = other - self.p1, other - self.p2 

1684 d = self.p2 - self.p1 

1685 # without the call to simplify, SymPy cannot tell that an expression 

1686 # like (a+b)*(a/2+b/2) is always non-negative. If it cannot be 

1687 # determined, raise an Undecidable error 

1688 try: 

1689 # the triangle inequality says that |d1|+|d2| >= |d| and is strict 

1690 # only if other lies in the line segment 

1691 return bool(simplify(Eq(abs(d1) + abs(d2) - abs(d), 0))) 

1692 except TypeError: 

1693 raise Undecidable("Cannot determine if {} is in {}".format(other, self)) 

1694 if isinstance(other, Segment): 

1695 return other.p1 in self and other.p2 in self 

1696 

1697 return False 

1698 

1699 def equals(self, other): 

1700 """Returns True if self and other are the same mathematical entities""" 

1701 return isinstance(other, self.func) and list( 

1702 ordered(self.args)) == list(ordered(other.args)) 

1703 

1704 def distance(self, other): 

1705 """ 

1706 Finds the shortest distance between a line segment and a point. 

1707 

1708 Raises 

1709 ====== 

1710 

1711 NotImplementedError is raised if `other` is not a Point 

1712 

1713 Examples 

1714 ======== 

1715 

1716 >>> from sympy import Point, Segment 

1717 >>> p1, p2 = Point(0, 1), Point(3, 4) 

1718 >>> s = Segment(p1, p2) 

1719 >>> s.distance(Point(10, 15)) 

1720 sqrt(170) 

1721 >>> s.distance((0, 12)) 

1722 sqrt(73) 

1723 >>> from sympy import Point3D, Segment3D 

1724 >>> p1, p2 = Point3D(0, 0, 3), Point3D(1, 1, 4) 

1725 >>> s = Segment3D(p1, p2) 

1726 >>> s.distance(Point3D(10, 15, 12)) 

1727 sqrt(341) 

1728 >>> s.distance((10, 15, 12)) 

1729 sqrt(341) 

1730 """ 

1731 if not isinstance(other, GeometryEntity): 

1732 other = Point(other, dim=self.ambient_dimension) 

1733 if isinstance(other, Point): 

1734 vp1 = other - self.p1 

1735 vp2 = other - self.p2 

1736 

1737 dot_prod_sign_1 = self.direction.dot(vp1) >= 0 

1738 dot_prod_sign_2 = self.direction.dot(vp2) <= 0 

1739 if dot_prod_sign_1 and dot_prod_sign_2: 

1740 return Line(self.p1, self.p2).distance(other) 

1741 if dot_prod_sign_1 and not dot_prod_sign_2: 

1742 return abs(vp2) 

1743 if not dot_prod_sign_1 and dot_prod_sign_2: 

1744 return abs(vp1) 

1745 raise NotImplementedError() 

1746 

1747 @property 

1748 def length(self): 

1749 """The length of the line segment. 

1750 

1751 See Also 

1752 ======== 

1753 

1754 sympy.geometry.point.Point.distance 

1755 

1756 Examples 

1757 ======== 

1758 

1759 >>> from sympy import Point, Segment 

1760 >>> p1, p2 = Point(0, 0), Point(4, 3) 

1761 >>> s1 = Segment(p1, p2) 

1762 >>> s1.length 

1763 5 

1764 >>> from sympy import Point3D, Segment3D 

1765 >>> p1, p2 = Point3D(0, 0, 0), Point3D(4, 3, 3) 

1766 >>> s1 = Segment3D(p1, p2) 

1767 >>> s1.length 

1768 sqrt(34) 

1769 

1770 """ 

1771 return Point.distance(self.p1, self.p2) 

1772 

1773 @property 

1774 def midpoint(self): 

1775 """The midpoint of the line segment. 

1776 

1777 See Also 

1778 ======== 

1779 

1780 sympy.geometry.point.Point.midpoint 

1781 

1782 Examples 

1783 ======== 

1784 

1785 >>> from sympy import Point, Segment 

1786 >>> p1, p2 = Point(0, 0), Point(4, 3) 

1787 >>> s1 = Segment(p1, p2) 

1788 >>> s1.midpoint 

1789 Point2D(2, 3/2) 

1790 >>> from sympy import Point3D, Segment3D 

1791 >>> p1, p2 = Point3D(0, 0, 0), Point3D(4, 3, 3) 

1792 >>> s1 = Segment3D(p1, p2) 

1793 >>> s1.midpoint 

1794 Point3D(2, 3/2, 3/2) 

1795 

1796 """ 

1797 return Point.midpoint(self.p1, self.p2) 

1798 

1799 def perpendicular_bisector(self, p=None): 

1800 """The perpendicular bisector of this segment. 

1801 

1802 If no point is specified or the point specified is not on the 

1803 bisector then the bisector is returned as a Line. Otherwise a 

1804 Segment is returned that joins the point specified and the 

1805 intersection of the bisector and the segment. 

1806 

1807 Parameters 

1808 ========== 

1809 

1810 p : Point 

1811 

1812 Returns 

1813 ======= 

1814 

1815 bisector : Line or Segment 

1816 

1817 See Also 

1818 ======== 

1819 

1820 LinearEntity.perpendicular_segment 

1821 

1822 Examples 

1823 ======== 

1824 

1825 >>> from sympy import Point, Segment 

1826 >>> p1, p2, p3 = Point(0, 0), Point(6, 6), Point(5, 1) 

1827 >>> s1 = Segment(p1, p2) 

1828 >>> s1.perpendicular_bisector() 

1829 Line2D(Point2D(3, 3), Point2D(-3, 9)) 

1830 

1831 >>> s1.perpendicular_bisector(p3) 

1832 Segment2D(Point2D(5, 1), Point2D(3, 3)) 

1833 

1834 """ 

1835 l = self.perpendicular_line(self.midpoint) 

1836 if p is not None: 

1837 p2 = Point(p, dim=self.ambient_dimension) 

1838 if p2 in l: 

1839 return Segment(p2, self.midpoint) 

1840 return l 

1841 

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

1843 """The plot interval for the default geometric plot of the Segment gives 

1844 values that will produce the full segment in a plot. 

1845 

1846 Parameters 

1847 ========== 

1848 

1849 parameter : str, optional 

1850 Default value is 't'. 

1851 

1852 Returns 

1853 ======= 

1854 

1855 plot_interval : list 

1856 [parameter, lower_bound, upper_bound] 

1857 

1858 Examples 

1859 ======== 

1860 

1861 >>> from sympy import Point, Segment 

1862 >>> p1, p2 = Point(0, 0), Point(5, 3) 

1863 >>> s1 = Segment(p1, p2) 

1864 >>> s1.plot_interval() 

1865 [t, 0, 1] 

1866 

1867 """ 

1868 t = _symbol(parameter, real=True) 

1869 return [t, 0, 1] 

1870 

1871 

1872class LinearEntity2D(LinearEntity): 

1873 """A base class for all linear entities (line, ray and segment) 

1874 in a 2-dimensional Euclidean space. 

1875 

1876 Attributes 

1877 ========== 

1878 

1879 p1 

1880 p2 

1881 coefficients 

1882 slope 

1883 points 

1884 

1885 Notes 

1886 ===== 

1887 

1888 This is an abstract class and is not meant to be instantiated. 

1889 

1890 See Also 

1891 ======== 

1892 

1893 sympy.geometry.entity.GeometryEntity 

1894 

1895 """ 

1896 @property 

1897 def bounds(self): 

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

1899 rectangle for the geometric figure. 

1900 

1901 """ 

1902 verts = self.points 

1903 xs = [p.x for p in verts] 

1904 ys = [p.y for p in verts] 

1905 return (min(xs), min(ys), max(xs), max(ys)) 

1906 

1907 def perpendicular_line(self, p): 

1908 """Create a new Line perpendicular to this linear entity which passes 

1909 through the point `p`. 

1910 

1911 Parameters 

1912 ========== 

1913 

1914 p : Point 

1915 

1916 Returns 

1917 ======= 

1918 

1919 line : Line 

1920 

1921 See Also 

1922 ======== 

1923 

1924 sympy.geometry.line.LinearEntity.is_perpendicular, perpendicular_segment 

1925 

1926 Examples 

1927 ======== 

1928 

1929 >>> from sympy import Point, Line 

1930 >>> p1, p2, p3 = Point(0, 0), Point(2, 3), Point(-2, 2) 

1931 >>> L = Line(p1, p2) 

1932 >>> P = L.perpendicular_line(p3); P 

1933 Line2D(Point2D(-2, 2), Point2D(-5, 4)) 

1934 >>> L.is_perpendicular(P) 

1935 True 

1936 

1937 In 2D, the first point of the perpendicular line is the 

1938 point through which was required to pass; the second 

1939 point is arbitrarily chosen. To get a line that explicitly 

1940 uses a point in the line, create a line from the perpendicular 

1941 segment from the line to the point: 

1942 

1943 >>> Line(L.perpendicular_segment(p3)) 

1944 Line2D(Point2D(-2, 2), Point2D(4/13, 6/13)) 

1945 """ 

1946 p = Point(p, dim=self.ambient_dimension) 

1947 # any two lines in R^2 intersect, so blindly making 

1948 # a line through p in an orthogonal direction will work 

1949 # and is faster than finding the projection point as in 3D 

1950 return Line(p, p + self.direction.orthogonal_direction) 

1951 

1952 @property 

1953 def slope(self): 

1954 """The slope of this linear entity, or infinity if vertical. 

1955 

1956 Returns 

1957 ======= 

1958 

1959 slope : number or SymPy expression 

1960 

1961 See Also 

1962 ======== 

1963 

1964 coefficients 

1965 

1966 Examples 

1967 ======== 

1968 

1969 >>> from sympy import Point, Line 

1970 >>> p1, p2 = Point(0, 0), Point(3, 5) 

1971 >>> l1 = Line(p1, p2) 

1972 >>> l1.slope 

1973 5/3 

1974 

1975 >>> p3 = Point(0, 4) 

1976 >>> l2 = Line(p1, p3) 

1977 >>> l2.slope 

1978 oo 

1979 

1980 """ 

1981 d1, d2 = (self.p1 - self.p2).args 

1982 if d1 == 0: 

1983 return S.Infinity 

1984 return simplify(d2/d1) 

1985 

1986 

1987class Line2D(LinearEntity2D, Line): 

1988 """An infinite line in space 2D. 

1989 

1990 A line is declared with two distinct points or a point and slope 

1991 as defined using keyword `slope`. 

1992 

1993 Parameters 

1994 ========== 

1995 

1996 p1 : Point 

1997 pt : Point 

1998 slope : SymPy expression 

1999 

2000 See Also 

2001 ======== 

2002 

2003 sympy.geometry.point.Point 

2004 

2005 Examples 

2006 ======== 

2007 

2008 >>> from sympy import Line, Segment, Point 

2009 >>> L = Line(Point(2,3), Point(3,5)) 

2010 >>> L 

2011 Line2D(Point2D(2, 3), Point2D(3, 5)) 

2012 >>> L.points 

2013 (Point2D(2, 3), Point2D(3, 5)) 

2014 >>> L.equation() 

2015 -2*x + y + 1 

2016 >>> L.coefficients 

2017 (-2, 1, 1) 

2018 

2019 Instantiate with keyword ``slope``: 

2020 

2021 >>> Line(Point(0, 0), slope=0) 

2022 Line2D(Point2D(0, 0), Point2D(1, 0)) 

2023 

2024 Instantiate with another linear object 

2025 

2026 >>> s = Segment((0, 0), (0, 1)) 

2027 >>> Line(s).equation() 

2028 x 

2029 """ 

2030 def __new__(cls, p1, pt=None, slope=None, **kwargs): 

2031 if isinstance(p1, LinearEntity): 

2032 if pt is not None: 

2033 raise ValueError('When p1 is a LinearEntity, pt should be None') 

2034 p1, pt = Point._normalize_dimension(*p1.args, dim=2) 

2035 else: 

2036 p1 = Point(p1, dim=2) 

2037 if pt is not None and slope is None: 

2038 try: 

2039 p2 = Point(pt, dim=2) 

2040 except (NotImplementedError, TypeError, ValueError): 

2041 raise ValueError(filldedent(''' 

2042 The 2nd argument was not a valid Point. 

2043 If it was a slope, enter it with keyword "slope". 

2044 ''')) 

2045 elif slope is not None and pt is None: 

2046 slope = sympify(slope) 

2047 if slope.is_finite is False: 

2048 # when infinite slope, don't change x 

2049 dx = 0 

2050 dy = 1 

2051 else: 

2052 # go over 1 up slope 

2053 dx = 1 

2054 dy = slope 

2055 # XXX avoiding simplification by adding to coords directly 

2056 p2 = Point(p1.x + dx, p1.y + dy, evaluate=False) 

2057 else: 

2058 raise ValueError('A 2nd Point or keyword "slope" must be used.') 

2059 return LinearEntity2D.__new__(cls, p1, p2, **kwargs) 

2060 

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

2062 """Returns SVG path element for the LinearEntity. 

2063 

2064 Parameters 

2065 ========== 

2066 

2067 scale_factor : float 

2068 Multiplication factor for the SVG stroke-width. Default is 1. 

2069 fill_color : str, optional 

2070 Hex string for fill color. Default is "#66cc99". 

2071 """ 

2072 verts = (N(self.p1), N(self.p2)) 

2073 coords = ["{},{}".format(p.x, p.y) for p in verts] 

2074 path = "M {} L {}".format(coords[0], " L ".join(coords[1:])) 

2075 

2076 return ( 

2077 '<path fill-rule="evenodd" fill="{2}" stroke="#555555" ' 

2078 'stroke-width="{0}" opacity="0.6" d="{1}" ' 

2079 'marker-start="url(#markerReverseArrow)" marker-end="url(#markerArrow)"/>' 

2080 ).format(2.*scale_factor, path, fill_color) 

2081 

2082 @property 

2083 def coefficients(self): 

2084 """The coefficients (`a`, `b`, `c`) for `ax + by + c = 0`. 

2085 

2086 See Also 

2087 ======== 

2088 

2089 sympy.geometry.line.Line2D.equation 

2090 

2091 Examples 

2092 ======== 

2093 

2094 >>> from sympy import Point, Line 

2095 >>> from sympy.abc import x, y 

2096 >>> p1, p2 = Point(0, 0), Point(5, 3) 

2097 >>> l = Line(p1, p2) 

2098 >>> l.coefficients 

2099 (-3, 5, 0) 

2100 

2101 >>> p3 = Point(x, y) 

2102 >>> l2 = Line(p1, p3) 

2103 >>> l2.coefficients 

2104 (-y, x, 0) 

2105 

2106 """ 

2107 p1, p2 = self.points 

2108 if p1.x == p2.x: 

2109 return (S.One, S.Zero, -p1.x) 

2110 elif p1.y == p2.y: 

2111 return (S.Zero, S.One, -p1.y) 

2112 return tuple([simplify(i) for i in 

2113 (self.p1.y - self.p2.y, 

2114 self.p2.x - self.p1.x, 

2115 self.p1.x*self.p2.y - self.p1.y*self.p2.x)]) 

2116 

2117 def equation(self, x='x', y='y'): 

2118 """The equation of the line: ax + by + c. 

2119 

2120 Parameters 

2121 ========== 

2122 

2123 x : str, optional 

2124 The name to use for the x-axis, default value is 'x'. 

2125 y : str, optional 

2126 The name to use for the y-axis, default value is 'y'. 

2127 

2128 Returns 

2129 ======= 

2130 

2131 equation : SymPy expression 

2132 

2133 See Also 

2134 ======== 

2135 

2136 sympy.geometry.line.Line2D.coefficients 

2137 

2138 Examples 

2139 ======== 

2140 

2141 >>> from sympy import Point, Line 

2142 >>> p1, p2 = Point(1, 0), Point(5, 3) 

2143 >>> l1 = Line(p1, p2) 

2144 >>> l1.equation() 

2145 -3*x + 4*y + 3 

2146 

2147 """ 

2148 x = _symbol(x, real=True) 

2149 y = _symbol(y, real=True) 

2150 p1, p2 = self.points 

2151 if p1.x == p2.x: 

2152 return x - p1.x 

2153 elif p1.y == p2.y: 

2154 return y - p1.y 

2155 

2156 a, b, c = self.coefficients 

2157 return a*x + b*y + c 

2158 

2159 

2160class Ray2D(LinearEntity2D, Ray): 

2161 """ 

2162 A Ray is a semi-line in the space with a source point and a direction. 

2163 

2164 Parameters 

2165 ========== 

2166 

2167 p1 : Point 

2168 The source of the Ray 

2169 p2 : Point or radian value 

2170 This point determines the direction in which the Ray propagates. 

2171 If given as an angle it is interpreted in radians with the positive 

2172 direction being ccw. 

2173 

2174 Attributes 

2175 ========== 

2176 

2177 source 

2178 xdirection 

2179 ydirection 

2180 

2181 See Also 

2182 ======== 

2183 

2184 sympy.geometry.point.Point, Line 

2185 

2186 Examples 

2187 ======== 

2188 

2189 >>> from sympy import Point, pi, Ray 

2190 >>> r = Ray(Point(2, 3), Point(3, 5)) 

2191 >>> r 

2192 Ray2D(Point2D(2, 3), Point2D(3, 5)) 

2193 >>> r.points 

2194 (Point2D(2, 3), Point2D(3, 5)) 

2195 >>> r.source 

2196 Point2D(2, 3) 

2197 >>> r.xdirection 

2198 oo 

2199 >>> r.ydirection 

2200 oo 

2201 >>> r.slope 

2202 2 

2203 >>> Ray(Point(0, 0), angle=pi/4).slope 

2204 1 

2205 

2206 """ 

2207 def __new__(cls, p1, pt=None, angle=None, **kwargs): 

2208 p1 = Point(p1, dim=2) 

2209 if pt is not None and angle is None: 

2210 try: 

2211 p2 = Point(pt, dim=2) 

2212 except (NotImplementedError, TypeError, ValueError): 

2213 raise ValueError(filldedent(''' 

2214 The 2nd argument was not a valid Point; if 

2215 it was meant to be an angle it should be 

2216 given with keyword "angle".''')) 

2217 if p1 == p2: 

2218 raise ValueError('A Ray requires two distinct points.') 

2219 elif angle is not None and pt is None: 

2220 # we need to know if the angle is an odd multiple of pi/2 

2221 angle = sympify(angle) 

2222 c = _pi_coeff(angle) 

2223 p2 = None 

2224 if c is not None: 

2225 if c.is_Rational: 

2226 if c.q == 2: 

2227 if c.p == 1: 

2228 p2 = p1 + Point(0, 1) 

2229 elif c.p == 3: 

2230 p2 = p1 + Point(0, -1) 

2231 elif c.q == 1: 

2232 if c.p == 0: 

2233 p2 = p1 + Point(1, 0) 

2234 elif c.p == 1: 

2235 p2 = p1 + Point(-1, 0) 

2236 if p2 is None: 

2237 c *= S.Pi 

2238 else: 

2239 c = angle % (2*S.Pi) 

2240 if not p2: 

2241 m = 2*c/S.Pi 

2242 left = And(1 < m, m < 3) # is it in quadrant 2 or 3? 

2243 x = Piecewise((-1, left), (Piecewise((0, Eq(m % 1, 0)), (1, True)), True)) 

2244 y = Piecewise((-tan(c), left), (Piecewise((1, Eq(m, 1)), (-1, Eq(m, 3)), (tan(c), True)), True)) 

2245 p2 = p1 + Point(x, y) 

2246 else: 

2247 raise ValueError('A 2nd point or keyword "angle" must be used.') 

2248 

2249 return LinearEntity2D.__new__(cls, p1, p2, **kwargs) 

2250 

2251 @property 

2252 def xdirection(self): 

2253 """The x direction of the ray. 

2254 

2255 Positive infinity if the ray points in the positive x direction, 

2256 negative infinity if the ray points in the negative x direction, 

2257 or 0 if the ray is vertical. 

2258 

2259 See Also 

2260 ======== 

2261 

2262 ydirection 

2263 

2264 Examples 

2265 ======== 

2266 

2267 >>> from sympy import Point, Ray 

2268 >>> p1, p2, p3 = Point(0, 0), Point(1, 1), Point(0, -1) 

2269 >>> r1, r2 = Ray(p1, p2), Ray(p1, p3) 

2270 >>> r1.xdirection 

2271 oo 

2272 >>> r2.xdirection 

2273 0 

2274 

2275 """ 

2276 if self.p1.x < self.p2.x: 

2277 return S.Infinity 

2278 elif self.p1.x == self.p2.x: 

2279 return S.Zero 

2280 else: 

2281 return S.NegativeInfinity 

2282 

2283 @property 

2284 def ydirection(self): 

2285 """The y direction of the ray. 

2286 

2287 Positive infinity if the ray points in the positive y direction, 

2288 negative infinity if the ray points in the negative y direction, 

2289 or 0 if the ray is horizontal. 

2290 

2291 See Also 

2292 ======== 

2293 

2294 xdirection 

2295 

2296 Examples 

2297 ======== 

2298 

2299 >>> from sympy import Point, Ray 

2300 >>> p1, p2, p3 = Point(0, 0), Point(-1, -1), Point(-1, 0) 

2301 >>> r1, r2 = Ray(p1, p2), Ray(p1, p3) 

2302 >>> r1.ydirection 

2303 -oo 

2304 >>> r2.ydirection 

2305 0 

2306 

2307 """ 

2308 if self.p1.y < self.p2.y: 

2309 return S.Infinity 

2310 elif self.p1.y == self.p2.y: 

2311 return S.Zero 

2312 else: 

2313 return S.NegativeInfinity 

2314 

2315 def closing_angle(r1, r2): 

2316 """Return the angle by which r2 must be rotated so it faces the same 

2317 direction as r1. 

2318 

2319 Parameters 

2320 ========== 

2321 

2322 r1 : Ray2D 

2323 r2 : Ray2D 

2324 

2325 Returns 

2326 ======= 

2327 

2328 angle : angle in radians (ccw angle is positive) 

2329 

2330 See Also 

2331 ======== 

2332 

2333 LinearEntity.angle_between 

2334 

2335 Examples 

2336 ======== 

2337 

2338 >>> from sympy import Ray, pi 

2339 >>> r1 = Ray((0, 0), (1, 0)) 

2340 >>> r2 = r1.rotate(-pi/2) 

2341 >>> angle = r1.closing_angle(r2); angle 

2342 pi/2 

2343 >>> r2.rotate(angle).direction.unit == r1.direction.unit 

2344 True 

2345 >>> r2.closing_angle(r1) 

2346 -pi/2 

2347 """ 

2348 if not all(isinstance(r, Ray2D) for r in (r1, r2)): 

2349 # although the direction property is defined for 

2350 # all linear entities, only the Ray is truly a 

2351 # directed object 

2352 raise TypeError('Both arguments must be Ray2D objects.') 

2353 

2354 a1 = atan2(*list(reversed(r1.direction.args))) 

2355 a2 = atan2(*list(reversed(r2.direction.args))) 

2356 if a1*a2 < 0: 

2357 a1 = 2*S.Pi + a1 if a1 < 0 else a1 

2358 a2 = 2*S.Pi + a2 if a2 < 0 else a2 

2359 return a1 - a2 

2360 

2361 

2362class Segment2D(LinearEntity2D, Segment): 

2363 """A line segment in 2D space. 

2364 

2365 Parameters 

2366 ========== 

2367 

2368 p1 : Point 

2369 p2 : Point 

2370 

2371 Attributes 

2372 ========== 

2373 

2374 length : number or SymPy expression 

2375 midpoint : Point 

2376 

2377 See Also 

2378 ======== 

2379 

2380 sympy.geometry.point.Point, Line 

2381 

2382 Examples 

2383 ======== 

2384 

2385 >>> from sympy import Point, Segment 

2386 >>> Segment((1, 0), (1, 1)) # tuples are interpreted as pts 

2387 Segment2D(Point2D(1, 0), Point2D(1, 1)) 

2388 >>> s = Segment(Point(4, 3), Point(1, 1)); s 

2389 Segment2D(Point2D(4, 3), Point2D(1, 1)) 

2390 >>> s.points 

2391 (Point2D(4, 3), Point2D(1, 1)) 

2392 >>> s.slope 

2393 2/3 

2394 >>> s.length 

2395 sqrt(13) 

2396 >>> s.midpoint 

2397 Point2D(5/2, 2) 

2398 

2399 """ 

2400 def __new__(cls, p1, p2, **kwargs): 

2401 p1 = Point(p1, dim=2) 

2402 p2 = Point(p2, dim=2) 

2403 

2404 if p1 == p2: 

2405 return p1 

2406 

2407 return LinearEntity2D.__new__(cls, p1, p2, **kwargs) 

2408 

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

2410 """Returns SVG path element for the LinearEntity. 

2411 

2412 Parameters 

2413 ========== 

2414 

2415 scale_factor : float 

2416 Multiplication factor for the SVG stroke-width. Default is 1. 

2417 fill_color : str, optional 

2418 Hex string for fill color. Default is "#66cc99". 

2419 """ 

2420 verts = (N(self.p1), N(self.p2)) 

2421 coords = ["{},{}".format(p.x, p.y) for p in verts] 

2422 path = "M {} L {}".format(coords[0], " L ".join(coords[1:])) 

2423 return ( 

2424 '<path fill-rule="evenodd" fill="{2}" stroke="#555555" ' 

2425 'stroke-width="{0}" opacity="0.6" d="{1}" />' 

2426 ).format(2.*scale_factor, path, fill_color) 

2427 

2428 

2429class LinearEntity3D(LinearEntity): 

2430 """An base class for all linear entities (line, ray and segment) 

2431 in a 3-dimensional Euclidean space. 

2432 

2433 Attributes 

2434 ========== 

2435 

2436 p1 

2437 p2 

2438 direction_ratio 

2439 direction_cosine 

2440 points 

2441 

2442 Notes 

2443 ===== 

2444 

2445 This is a base class and is not meant to be instantiated. 

2446 """ 

2447 def __new__(cls, p1, p2, **kwargs): 

2448 p1 = Point3D(p1, dim=3) 

2449 p2 = Point3D(p2, dim=3) 

2450 if p1 == p2: 

2451 # if it makes sense to return a Point, handle in subclass 

2452 raise ValueError( 

2453 "%s.__new__ requires two unique Points." % cls.__name__) 

2454 

2455 return GeometryEntity.__new__(cls, p1, p2, **kwargs) 

2456 

2457 ambient_dimension = 3 

2458 

2459 @property 

2460 def direction_ratio(self): 

2461 """The direction ratio of a given line in 3D. 

2462 

2463 See Also 

2464 ======== 

2465 

2466 sympy.geometry.line.Line3D.equation 

2467 

2468 Examples 

2469 ======== 

2470 

2471 >>> from sympy import Point3D, Line3D 

2472 >>> p1, p2 = Point3D(0, 0, 0), Point3D(5, 3, 1) 

2473 >>> l = Line3D(p1, p2) 

2474 >>> l.direction_ratio 

2475 [5, 3, 1] 

2476 """ 

2477 p1, p2 = self.points 

2478 return p1.direction_ratio(p2) 

2479 

2480 @property 

2481 def direction_cosine(self): 

2482 """The normalized direction ratio of a given line in 3D. 

2483 

2484 See Also 

2485 ======== 

2486 

2487 sympy.geometry.line.Line3D.equation 

2488 

2489 Examples 

2490 ======== 

2491 

2492 >>> from sympy import Point3D, Line3D 

2493 >>> p1, p2 = Point3D(0, 0, 0), Point3D(5, 3, 1) 

2494 >>> l = Line3D(p1, p2) 

2495 >>> l.direction_cosine 

2496 [sqrt(35)/7, 3*sqrt(35)/35, sqrt(35)/35] 

2497 >>> sum(i**2 for i in _) 

2498 1 

2499 """ 

2500 p1, p2 = self.points 

2501 return p1.direction_cosine(p2) 

2502 

2503 

2504class Line3D(LinearEntity3D, Line): 

2505 """An infinite 3D line in space. 

2506 

2507 A line is declared with two distinct points or a point and direction_ratio 

2508 as defined using keyword `direction_ratio`. 

2509 

2510 Parameters 

2511 ========== 

2512 

2513 p1 : Point3D 

2514 pt : Point3D 

2515 direction_ratio : list 

2516 

2517 See Also 

2518 ======== 

2519 

2520 sympy.geometry.point.Point3D 

2521 sympy.geometry.line.Line 

2522 sympy.geometry.line.Line2D 

2523 

2524 Examples 

2525 ======== 

2526 

2527 >>> from sympy import Line3D, Point3D 

2528 >>> L = Line3D(Point3D(2, 3, 4), Point3D(3, 5, 1)) 

2529 >>> L 

2530 Line3D(Point3D(2, 3, 4), Point3D(3, 5, 1)) 

2531 >>> L.points 

2532 (Point3D(2, 3, 4), Point3D(3, 5, 1)) 

2533 """ 

2534 def __new__(cls, p1, pt=None, direction_ratio=(), **kwargs): 

2535 if isinstance(p1, LinearEntity3D): 

2536 if pt is not None: 

2537 raise ValueError('if p1 is a LinearEntity, pt must be None.') 

2538 p1, pt = p1.args 

2539 else: 

2540 p1 = Point(p1, dim=3) 

2541 if pt is not None and len(direction_ratio) == 0: 

2542 pt = Point(pt, dim=3) 

2543 elif len(direction_ratio) == 3 and pt is None: 

2544 pt = Point3D(p1.x + direction_ratio[0], p1.y + direction_ratio[1], 

2545 p1.z + direction_ratio[2]) 

2546 else: 

2547 raise ValueError('A 2nd Point or keyword "direction_ratio" must ' 

2548 'be used.') 

2549 

2550 return LinearEntity3D.__new__(cls, p1, pt, **kwargs) 

2551 

2552 def equation(self, x='x', y='y', z='z'): 

2553 """Return the equations that define the line in 3D. 

2554 

2555 Parameters 

2556 ========== 

2557 

2558 x : str, optional 

2559 The name to use for the x-axis, default value is 'x'. 

2560 y : str, optional 

2561 The name to use for the y-axis, default value is 'y'. 

2562 z : str, optional 

2563 The name to use for the z-axis, default value is 'z'. 

2564 

2565 Returns 

2566 ======= 

2567 

2568 equation : Tuple of simultaneous equations 

2569 

2570 Examples 

2571 ======== 

2572 

2573 >>> from sympy import Point3D, Line3D, solve 

2574 >>> from sympy.abc import x, y, z 

2575 >>> p1, p2 = Point3D(1, 0, 0), Point3D(5, 3, 0) 

2576 >>> l1 = Line3D(p1, p2) 

2577 >>> eq = l1.equation(x, y, z); eq 

2578 (-3*x + 4*y + 3, z) 

2579 >>> solve(eq.subs(z, 0), (x, y, z)) 

2580 {x: 4*y/3 + 1} 

2581 """ 

2582 x, y, z, k = [_symbol(i, real=True) for i in (x, y, z, 'k')] 

2583 p1, p2 = self.points 

2584 d1, d2, d3 = p1.direction_ratio(p2) 

2585 x1, y1, z1 = p1 

2586 eqs = [-d1*k + x - x1, -d2*k + y - y1, -d3*k + z - z1] 

2587 # eliminate k from equations by solving first eq with k for k 

2588 for i, e in enumerate(eqs): 

2589 if e.has(k): 

2590 kk = solve(eqs[i], k)[0] 

2591 eqs.pop(i) 

2592 break 

2593 return Tuple(*[i.subs(k, kk).as_numer_denom()[0] for i in eqs]) 

2594 

2595 

2596class Ray3D(LinearEntity3D, Ray): 

2597 """ 

2598 A Ray is a semi-line in the space with a source point and a direction. 

2599 

2600 Parameters 

2601 ========== 

2602 

2603 p1 : Point3D 

2604 The source of the Ray 

2605 p2 : Point or a direction vector 

2606 direction_ratio: Determines the direction in which the Ray propagates. 

2607 

2608 

2609 Attributes 

2610 ========== 

2611 

2612 source 

2613 xdirection 

2614 ydirection 

2615 zdirection 

2616 

2617 See Also 

2618 ======== 

2619 

2620 sympy.geometry.point.Point3D, Line3D 

2621 

2622 

2623 Examples 

2624 ======== 

2625 

2626 >>> from sympy import Point3D, Ray3D 

2627 >>> r = Ray3D(Point3D(2, 3, 4), Point3D(3, 5, 0)) 

2628 >>> r 

2629 Ray3D(Point3D(2, 3, 4), Point3D(3, 5, 0)) 

2630 >>> r.points 

2631 (Point3D(2, 3, 4), Point3D(3, 5, 0)) 

2632 >>> r.source 

2633 Point3D(2, 3, 4) 

2634 >>> r.xdirection 

2635 oo 

2636 >>> r.ydirection 

2637 oo 

2638 >>> r.direction_ratio 

2639 [1, 2, -4] 

2640 

2641 """ 

2642 def __new__(cls, p1, pt=None, direction_ratio=(), **kwargs): 

2643 if isinstance(p1, LinearEntity3D): 

2644 if pt is not None: 

2645 raise ValueError('If p1 is a LinearEntity, pt must be None') 

2646 p1, pt = p1.args 

2647 else: 

2648 p1 = Point(p1, dim=3) 

2649 if pt is not None and len(direction_ratio) == 0: 

2650 pt = Point(pt, dim=3) 

2651 elif len(direction_ratio) == 3 and pt is None: 

2652 pt = Point3D(p1.x + direction_ratio[0], p1.y + direction_ratio[1], 

2653 p1.z + direction_ratio[2]) 

2654 else: 

2655 raise ValueError(filldedent(''' 

2656 A 2nd Point or keyword "direction_ratio" must be used. 

2657 ''')) 

2658 

2659 return LinearEntity3D.__new__(cls, p1, pt, **kwargs) 

2660 

2661 @property 

2662 def xdirection(self): 

2663 """The x direction of the ray. 

2664 

2665 Positive infinity if the ray points in the positive x direction, 

2666 negative infinity if the ray points in the negative x direction, 

2667 or 0 if the ray is vertical. 

2668 

2669 See Also 

2670 ======== 

2671 

2672 ydirection 

2673 

2674 Examples 

2675 ======== 

2676 

2677 >>> from sympy import Point3D, Ray3D 

2678 >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(1, 1, 1), Point3D(0, -1, 0) 

2679 >>> r1, r2 = Ray3D(p1, p2), Ray3D(p1, p3) 

2680 >>> r1.xdirection 

2681 oo 

2682 >>> r2.xdirection 

2683 0 

2684 

2685 """ 

2686 if self.p1.x < self.p2.x: 

2687 return S.Infinity 

2688 elif self.p1.x == self.p2.x: 

2689 return S.Zero 

2690 else: 

2691 return S.NegativeInfinity 

2692 

2693 @property 

2694 def ydirection(self): 

2695 """The y direction of the ray. 

2696 

2697 Positive infinity if the ray points in the positive y direction, 

2698 negative infinity if the ray points in the negative y direction, 

2699 or 0 if the ray is horizontal. 

2700 

2701 See Also 

2702 ======== 

2703 

2704 xdirection 

2705 

2706 Examples 

2707 ======== 

2708 

2709 >>> from sympy import Point3D, Ray3D 

2710 >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(-1, -1, -1), Point3D(-1, 0, 0) 

2711 >>> r1, r2 = Ray3D(p1, p2), Ray3D(p1, p3) 

2712 >>> r1.ydirection 

2713 -oo 

2714 >>> r2.ydirection 

2715 0 

2716 

2717 """ 

2718 if self.p1.y < self.p2.y: 

2719 return S.Infinity 

2720 elif self.p1.y == self.p2.y: 

2721 return S.Zero 

2722 else: 

2723 return S.NegativeInfinity 

2724 

2725 @property 

2726 def zdirection(self): 

2727 """The z direction of the ray. 

2728 

2729 Positive infinity if the ray points in the positive z direction, 

2730 negative infinity if the ray points in the negative z direction, 

2731 or 0 if the ray is horizontal. 

2732 

2733 See Also 

2734 ======== 

2735 

2736 xdirection 

2737 

2738 Examples 

2739 ======== 

2740 

2741 >>> from sympy import Point3D, Ray3D 

2742 >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(-1, -1, -1), Point3D(-1, 0, 0) 

2743 >>> r1, r2 = Ray3D(p1, p2), Ray3D(p1, p3) 

2744 >>> r1.ydirection 

2745 -oo 

2746 >>> r2.ydirection 

2747 0 

2748 >>> r2.zdirection 

2749 0 

2750 

2751 """ 

2752 if self.p1.z < self.p2.z: 

2753 return S.Infinity 

2754 elif self.p1.z == self.p2.z: 

2755 return S.Zero 

2756 else: 

2757 return S.NegativeInfinity 

2758 

2759 

2760class Segment3D(LinearEntity3D, Segment): 

2761 """A line segment in a 3D space. 

2762 

2763 Parameters 

2764 ========== 

2765 

2766 p1 : Point3D 

2767 p2 : Point3D 

2768 

2769 Attributes 

2770 ========== 

2771 

2772 length : number or SymPy expression 

2773 midpoint : Point3D 

2774 

2775 See Also 

2776 ======== 

2777 

2778 sympy.geometry.point.Point3D, Line3D 

2779 

2780 Examples 

2781 ======== 

2782 

2783 >>> from sympy import Point3D, Segment3D 

2784 >>> Segment3D((1, 0, 0), (1, 1, 1)) # tuples are interpreted as pts 

2785 Segment3D(Point3D(1, 0, 0), Point3D(1, 1, 1)) 

2786 >>> s = Segment3D(Point3D(4, 3, 9), Point3D(1, 1, 7)); s 

2787 Segment3D(Point3D(4, 3, 9), Point3D(1, 1, 7)) 

2788 >>> s.points 

2789 (Point3D(4, 3, 9), Point3D(1, 1, 7)) 

2790 >>> s.length 

2791 sqrt(17) 

2792 >>> s.midpoint 

2793 Point3D(5/2, 2, 8) 

2794 

2795 """ 

2796 def __new__(cls, p1, p2, **kwargs): 

2797 p1 = Point(p1, dim=3) 

2798 p2 = Point(p2, dim=3) 

2799 

2800 if p1 == p2: 

2801 return p1 

2802 

2803 return LinearEntity3D.__new__(cls, p1, p2, **kwargs)