Coverage for /usr/lib/python3/dist-packages/sympy/geometry/point.py: 30%

338 statements  

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

1"""Geometrical Points. 

2 

3Contains 

4======== 

5Point 

6Point2D 

7Point3D 

8 

9When methods of Point require 1 or more points as arguments, they 

10can be passed as a sequence of coordinates or Points: 

11 

12>>> from sympy import Point 

13>>> Point(1, 1).is_collinear((2, 2), (3, 4)) 

14False 

15>>> Point(1, 1).is_collinear(Point(2, 2), Point(3, 4)) 

16False 

17 

18""" 

19 

20import warnings 

21 

22from sympy.core import S, sympify, Expr 

23from sympy.core.add import Add 

24from sympy.core.containers import Tuple 

25from sympy.core.numbers import Float 

26from sympy.core.parameters import global_parameters 

27from sympy.simplify import nsimplify, simplify 

28from sympy.geometry.exceptions import GeometryError 

29from sympy.functions.elementary.miscellaneous import sqrt 

30from sympy.functions.elementary.complexes import im 

31from sympy.functions.elementary.trigonometric import cos, sin 

32from sympy.matrices import Matrix 

33from sympy.matrices.expressions import Transpose 

34from sympy.utilities.iterables import uniq, is_sequence 

35from sympy.utilities.misc import filldedent, func_name, Undecidable 

36 

37from .entity import GeometryEntity 

38 

39from mpmath.libmp.libmpf import prec_to_dps 

40 

41 

42class Point(GeometryEntity): 

43 """A point in a n-dimensional Euclidean space. 

44 

45 Parameters 

46 ========== 

47 

48 coords : sequence of n-coordinate values. In the special 

49 case where n=2 or 3, a Point2D or Point3D will be created 

50 as appropriate. 

51 evaluate : if `True` (default), all floats are turn into 

52 exact types. 

53 dim : number of coordinates the point should have. If coordinates 

54 are unspecified, they are padded with zeros. 

55 on_morph : indicates what should happen when the number of 

56 coordinates of a point need to be changed by adding or 

57 removing zeros. Possible values are `'warn'`, `'error'`, or 

58 `ignore` (default). No warning or error is given when `*args` 

59 is empty and `dim` is given. An error is always raised when 

60 trying to remove nonzero coordinates. 

61 

62 

63 Attributes 

64 ========== 

65 

66 length 

67 origin: A `Point` representing the origin of the 

68 appropriately-dimensioned space. 

69 

70 Raises 

71 ====== 

72 

73 TypeError : When instantiating with anything but a Point or sequence 

74 ValueError : when instantiating with a sequence with length < 2 or 

75 when trying to reduce dimensions if keyword `on_morph='error'` is 

76 set. 

77 

78 See Also 

79 ======== 

80 

81 sympy.geometry.line.Segment : Connects two Points 

82 

83 Examples 

84 ======== 

85 

86 >>> from sympy import Point 

87 >>> from sympy.abc import x 

88 >>> Point(1, 2, 3) 

89 Point3D(1, 2, 3) 

90 >>> Point([1, 2]) 

91 Point2D(1, 2) 

92 >>> Point(0, x) 

93 Point2D(0, x) 

94 >>> Point(dim=4) 

95 Point(0, 0, 0, 0) 

96 

97 Floats are automatically converted to Rational unless the 

98 evaluate flag is False: 

99 

100 >>> Point(0.5, 0.25) 

101 Point2D(1/2, 1/4) 

102 >>> Point(0.5, 0.25, evaluate=False) 

103 Point2D(0.5, 0.25) 

104 

105 """ 

106 

107 is_Point = True 

108 

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

110 evaluate = kwargs.get('evaluate', global_parameters.evaluate) 

111 on_morph = kwargs.get('on_morph', 'ignore') 

112 

113 # unpack into coords 

114 coords = args[0] if len(args) == 1 else args 

115 

116 # check args and handle quickly handle Point instances 

117 if isinstance(coords, Point): 

118 # even if we're mutating the dimension of a point, we 

119 # don't reevaluate its coordinates 

120 evaluate = False 

121 if len(coords) == kwargs.get('dim', len(coords)): 

122 return coords 

123 

124 if not is_sequence(coords): 

125 raise TypeError(filldedent(''' 

126 Expecting sequence of coordinates, not `{}`''' 

127 .format(func_name(coords)))) 

128 # A point where only `dim` is specified is initialized 

129 # to zeros. 

130 if len(coords) == 0 and kwargs.get('dim', None): 

131 coords = (S.Zero,)*kwargs.get('dim') 

132 

133 coords = Tuple(*coords) 

134 dim = kwargs.get('dim', len(coords)) 

135 

136 if len(coords) < 2: 

137 raise ValueError(filldedent(''' 

138 Point requires 2 or more coordinates or 

139 keyword `dim` > 1.''')) 

140 if len(coords) != dim: 

141 message = ("Dimension of {} needs to be changed " 

142 "from {} to {}.").format(coords, len(coords), dim) 

143 if on_morph == 'ignore': 

144 pass 

145 elif on_morph == "error": 

146 raise ValueError(message) 

147 elif on_morph == 'warn': 

148 warnings.warn(message, stacklevel=2) 

149 else: 

150 raise ValueError(filldedent(''' 

151 on_morph value should be 'error', 

152 'warn' or 'ignore'.''')) 

153 if any(coords[dim:]): 

154 raise ValueError('Nonzero coordinates cannot be removed.') 

155 if any(a.is_number and im(a).is_zero is False for a in coords): 

156 raise ValueError('Imaginary coordinates are not permitted.') 

157 if not all(isinstance(a, Expr) for a in coords): 

158 raise TypeError('Coordinates must be valid SymPy expressions.') 

159 

160 # pad with zeros appropriately 

161 coords = coords[:dim] + (S.Zero,)*(dim - len(coords)) 

162 

163 # Turn any Floats into rationals and simplify 

164 # any expressions before we instantiate 

165 if evaluate: 

166 coords = coords.xreplace({ 

167 f: simplify(nsimplify(f, rational=True)) 

168 for f in coords.atoms(Float)}) 

169 

170 # return 2D or 3D instances 

171 if len(coords) == 2: 

172 kwargs['_nocheck'] = True 

173 return Point2D(*coords, **kwargs) 

174 elif len(coords) == 3: 

175 kwargs['_nocheck'] = True 

176 return Point3D(*coords, **kwargs) 

177 

178 # the general Point 

179 return GeometryEntity.__new__(cls, *coords) 

180 

181 def __abs__(self): 

182 """Returns the distance between this point and the origin.""" 

183 origin = Point([0]*len(self)) 

184 return Point.distance(origin, self) 

185 

186 def __add__(self, other): 

187 """Add other to self by incrementing self's coordinates by 

188 those of other. 

189 

190 Notes 

191 ===== 

192 

193 >>> from sympy import Point 

194 

195 When sequences of coordinates are passed to Point methods, they 

196 are converted to a Point internally. This __add__ method does 

197 not do that so if floating point values are used, a floating 

198 point result (in terms of SymPy Floats) will be returned. 

199 

200 >>> Point(1, 2) + (.1, .2) 

201 Point2D(1.1, 2.2) 

202 

203 If this is not desired, the `translate` method can be used or 

204 another Point can be added: 

205 

206 >>> Point(1, 2).translate(.1, .2) 

207 Point2D(11/10, 11/5) 

208 >>> Point(1, 2) + Point(.1, .2) 

209 Point2D(11/10, 11/5) 

210 

211 See Also 

212 ======== 

213 

214 sympy.geometry.point.Point.translate 

215 

216 """ 

217 try: 

218 s, o = Point._normalize_dimension(self, Point(other, evaluate=False)) 

219 except TypeError: 

220 raise GeometryError("Don't know how to add {} and a Point object".format(other)) 

221 

222 coords = [simplify(a + b) for a, b in zip(s, o)] 

223 return Point(coords, evaluate=False) 

224 

225 def __contains__(self, item): 

226 return item in self.args 

227 

228 def __truediv__(self, divisor): 

229 """Divide point's coordinates by a factor.""" 

230 divisor = sympify(divisor) 

231 coords = [simplify(x/divisor) for x in self.args] 

232 return Point(coords, evaluate=False) 

233 

234 def __eq__(self, other): 

235 if not isinstance(other, Point) or len(self.args) != len(other.args): 

236 return False 

237 return self.args == other.args 

238 

239 def __getitem__(self, key): 

240 return self.args[key] 

241 

242 def __hash__(self): 

243 return hash(self.args) 

244 

245 def __iter__(self): 

246 return self.args.__iter__() 

247 

248 def __len__(self): 

249 return len(self.args) 

250 

251 def __mul__(self, factor): 

252 """Multiply point's coordinates by a factor. 

253 

254 Notes 

255 ===== 

256 

257 >>> from sympy import Point 

258 

259 When multiplying a Point by a floating point number, 

260 the coordinates of the Point will be changed to Floats: 

261 

262 >>> Point(1, 2)*0.1 

263 Point2D(0.1, 0.2) 

264 

265 If this is not desired, the `scale` method can be used or 

266 else only multiply or divide by integers: 

267 

268 >>> Point(1, 2).scale(1.1, 1.1) 

269 Point2D(11/10, 11/5) 

270 >>> Point(1, 2)*11/10 

271 Point2D(11/10, 11/5) 

272 

273 See Also 

274 ======== 

275 

276 sympy.geometry.point.Point.scale 

277 """ 

278 factor = sympify(factor) 

279 coords = [simplify(x*factor) for x in self.args] 

280 return Point(coords, evaluate=False) 

281 

282 def __rmul__(self, factor): 

283 """Multiply a factor by point's coordinates.""" 

284 return self.__mul__(factor) 

285 

286 def __neg__(self): 

287 """Negate the point.""" 

288 coords = [-x for x in self.args] 

289 return Point(coords, evaluate=False) 

290 

291 def __sub__(self, other): 

292 """Subtract two points, or subtract a factor from this point's 

293 coordinates.""" 

294 return self + [-x for x in other] 

295 

296 @classmethod 

297 def _normalize_dimension(cls, *points, **kwargs): 

298 """Ensure that points have the same dimension. 

299 By default `on_morph='warn'` is passed to the 

300 `Point` constructor.""" 

301 # if we have a built-in ambient dimension, use it 

302 dim = getattr(cls, '_ambient_dimension', None) 

303 # override if we specified it 

304 dim = kwargs.get('dim', dim) 

305 # if no dim was given, use the highest dimensional point 

306 if dim is None: 

307 dim = max(i.ambient_dimension for i in points) 

308 if all(i.ambient_dimension == dim for i in points): 

309 return list(points) 

310 kwargs['dim'] = dim 

311 kwargs['on_morph'] = kwargs.get('on_morph', 'warn') 

312 return [Point(i, **kwargs) for i in points] 

313 

314 @staticmethod 

315 def affine_rank(*args): 

316 """The affine rank of a set of points is the dimension 

317 of the smallest affine space containing all the points. 

318 For example, if the points lie on a line (and are not all 

319 the same) their affine rank is 1. If the points lie on a plane 

320 but not a line, their affine rank is 2. By convention, the empty 

321 set has affine rank -1.""" 

322 

323 if len(args) == 0: 

324 return -1 

325 # make sure we're genuinely points 

326 # and translate every point to the origin 

327 points = Point._normalize_dimension(*[Point(i) for i in args]) 

328 origin = points[0] 

329 points = [i - origin for i in points[1:]] 

330 

331 m = Matrix([i.args for i in points]) 

332 # XXX fragile -- what is a better way? 

333 return m.rank(iszerofunc = lambda x: 

334 abs(x.n(2)) < 1e-12 if x.is_number else x.is_zero) 

335 

336 @property 

337 def ambient_dimension(self): 

338 """Number of components this point has.""" 

339 return getattr(self, '_ambient_dimension', len(self)) 

340 

341 @classmethod 

342 def are_coplanar(cls, *points): 

343 """Return True if there exists a plane in which all the points 

344 lie. A trivial True value is returned if `len(points) < 3` or 

345 all Points are 2-dimensional. 

346 

347 Parameters 

348 ========== 

349 

350 A set of points 

351 

352 Raises 

353 ====== 

354 

355 ValueError : if less than 3 unique points are given 

356 

357 Returns 

358 ======= 

359 

360 boolean 

361 

362 Examples 

363 ======== 

364 

365 >>> from sympy import Point3D 

366 >>> p1 = Point3D(1, 2, 2) 

367 >>> p2 = Point3D(2, 7, 2) 

368 >>> p3 = Point3D(0, 0, 2) 

369 >>> p4 = Point3D(1, 1, 2) 

370 >>> Point3D.are_coplanar(p1, p2, p3, p4) 

371 True 

372 >>> p5 = Point3D(0, 1, 3) 

373 >>> Point3D.are_coplanar(p1, p2, p3, p5) 

374 False 

375 

376 """ 

377 if len(points) <= 1: 

378 return True 

379 

380 points = cls._normalize_dimension(*[Point(i) for i in points]) 

381 # quick exit if we are in 2D 

382 if points[0].ambient_dimension == 2: 

383 return True 

384 points = list(uniq(points)) 

385 return Point.affine_rank(*points) <= 2 

386 

387 def distance(self, other): 

388 """The Euclidean distance between self and another GeometricEntity. 

389 

390 Returns 

391 ======= 

392 

393 distance : number or symbolic expression. 

394 

395 Raises 

396 ====== 

397 

398 TypeError : if other is not recognized as a GeometricEntity or is a 

399 GeometricEntity for which distance is not defined. 

400 

401 See Also 

402 ======== 

403 

404 sympy.geometry.line.Segment.length 

405 sympy.geometry.point.Point.taxicab_distance 

406 

407 Examples 

408 ======== 

409 

410 >>> from sympy import Point, Line 

411 >>> p1, p2 = Point(1, 1), Point(4, 5) 

412 >>> l = Line((3, 1), (2, 2)) 

413 >>> p1.distance(p2) 

414 5 

415 >>> p1.distance(l) 

416 sqrt(2) 

417 

418 The computed distance may be symbolic, too: 

419 

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

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

422 >>> p3.distance((0, 0)) 

423 sqrt(x**2 + y**2) 

424 

425 """ 

426 if not isinstance(other, GeometryEntity): 

427 try: 

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

429 except TypeError: 

430 raise TypeError("not recognized as a GeometricEntity: %s" % type(other)) 

431 if isinstance(other, Point): 

432 s, p = Point._normalize_dimension(self, Point(other)) 

433 return sqrt(Add(*((a - b)**2 for a, b in zip(s, p)))) 

434 distance = getattr(other, 'distance', None) 

435 if distance is None: 

436 raise TypeError("distance between Point and %s is not defined" % type(other)) 

437 return distance(self) 

438 

439 def dot(self, p): 

440 """Return dot product of self with another Point.""" 

441 if not is_sequence(p): 

442 p = Point(p) # raise the error via Point 

443 return Add(*(a*b for a, b in zip(self, p))) 

444 

445 def equals(self, other): 

446 """Returns whether the coordinates of self and other agree.""" 

447 # a point is equal to another point if all its components are equal 

448 if not isinstance(other, Point) or len(self) != len(other): 

449 return False 

450 return all(a.equals(b) for a, b in zip(self, other)) 

451 

452 def _eval_evalf(self, prec=15, **options): 

453 """Evaluate the coordinates of the point. 

454 

455 This method will, where possible, create and return a new Point 

456 where the coordinates are evaluated as floating point numbers to 

457 the precision indicated (default=15). 

458 

459 Parameters 

460 ========== 

461 

462 prec : int 

463 

464 Returns 

465 ======= 

466 

467 point : Point 

468 

469 Examples 

470 ======== 

471 

472 >>> from sympy import Point, Rational 

473 >>> p1 = Point(Rational(1, 2), Rational(3, 2)) 

474 >>> p1 

475 Point2D(1/2, 3/2) 

476 >>> p1.evalf() 

477 Point2D(0.5, 1.5) 

478 

479 """ 

480 dps = prec_to_dps(prec) 

481 coords = [x.evalf(n=dps, **options) for x in self.args] 

482 return Point(*coords, evaluate=False) 

483 

484 def intersection(self, other): 

485 """The intersection between this point and another GeometryEntity. 

486 

487 Parameters 

488 ========== 

489 

490 other : GeometryEntity or sequence of coordinates 

491 

492 Returns 

493 ======= 

494 

495 intersection : list of Points 

496 

497 Notes 

498 ===== 

499 

500 The return value will either be an empty list if there is no 

501 intersection, otherwise it will contain this point. 

502 

503 Examples 

504 ======== 

505 

506 >>> from sympy import Point 

507 >>> p1, p2, p3 = Point(0, 0), Point(1, 1), Point(0, 0) 

508 >>> p1.intersection(p2) 

509 [] 

510 >>> p1.intersection(p3) 

511 [Point2D(0, 0)] 

512 

513 """ 

514 if not isinstance(other, GeometryEntity): 

515 other = Point(other) 

516 if isinstance(other, Point): 

517 if self == other: 

518 return [self] 

519 p1, p2 = Point._normalize_dimension(self, other) 

520 if p1 == self and p1 == p2: 

521 return [self] 

522 return [] 

523 return other.intersection(self) 

524 

525 def is_collinear(self, *args): 

526 """Returns `True` if there exists a line 

527 that contains `self` and `points`. Returns `False` otherwise. 

528 A trivially True value is returned if no points are given. 

529 

530 Parameters 

531 ========== 

532 

533 args : sequence of Points 

534 

535 Returns 

536 ======= 

537 

538 is_collinear : boolean 

539 

540 See Also 

541 ======== 

542 

543 sympy.geometry.line.Line 

544 

545 Examples 

546 ======== 

547 

548 >>> from sympy import Point 

549 >>> from sympy.abc import x 

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

551 >>> p3, p4, p5 = Point(2, 2), Point(x, x), Point(1, 2) 

552 >>> Point.is_collinear(p1, p2, p3, p4) 

553 True 

554 >>> Point.is_collinear(p1, p2, p3, p5) 

555 False 

556 

557 """ 

558 points = (self,) + args 

559 points = Point._normalize_dimension(*[Point(i) for i in points]) 

560 points = list(uniq(points)) 

561 return Point.affine_rank(*points) <= 1 

562 

563 def is_concyclic(self, *args): 

564 """Do `self` and the given sequence of points lie in a circle? 

565 

566 Returns True if the set of points are concyclic and 

567 False otherwise. A trivial value of True is returned 

568 if there are fewer than 2 other points. 

569 

570 Parameters 

571 ========== 

572 

573 args : sequence of Points 

574 

575 Returns 

576 ======= 

577 

578 is_concyclic : boolean 

579 

580 

581 Examples 

582 ======== 

583 

584 >>> from sympy import Point 

585 

586 Define 4 points that are on the unit circle: 

587 

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

589 

590 >>> p1.is_concyclic() == p1.is_concyclic(p2, p3, p4) == True 

591 True 

592 

593 Define a point not on that circle: 

594 

595 >>> p = Point(1, 1) 

596 

597 >>> p.is_concyclic(p1, p2, p3) 

598 False 

599 

600 """ 

601 points = (self,) + args 

602 points = Point._normalize_dimension(*[Point(i) for i in points]) 

603 points = list(uniq(points)) 

604 if not Point.affine_rank(*points) <= 2: 

605 return False 

606 origin = points[0] 

607 points = [p - origin for p in points] 

608 # points are concyclic if they are coplanar and 

609 # there is a point c so that ||p_i-c|| == ||p_j-c|| for all 

610 # i and j. Rearranging this equation gives us the following 

611 # condition: the matrix `mat` must not a pivot in the last 

612 # column. 

613 mat = Matrix([list(i) + [i.dot(i)] for i in points]) 

614 rref, pivots = mat.rref() 

615 if len(origin) not in pivots: 

616 return True 

617 return False 

618 

619 @property 

620 def is_nonzero(self): 

621 """True if any coordinate is nonzero, False if every coordinate is zero, 

622 and None if it cannot be determined.""" 

623 is_zero = self.is_zero 

624 if is_zero is None: 

625 return None 

626 return not is_zero 

627 

628 def is_scalar_multiple(self, p): 

629 """Returns whether each coordinate of `self` is a scalar 

630 multiple of the corresponding coordinate in point p. 

631 """ 

632 s, o = Point._normalize_dimension(self, Point(p)) 

633 # 2d points happen a lot, so optimize this function call 

634 if s.ambient_dimension == 2: 

635 (x1, y1), (x2, y2) = s.args, o.args 

636 rv = (x1*y2 - x2*y1).equals(0) 

637 if rv is None: 

638 raise Undecidable(filldedent( 

639 '''Cannot determine if %s is a scalar multiple of 

640 %s''' % (s, o))) 

641 

642 # if the vectors p1 and p2 are linearly dependent, then they must 

643 # be scalar multiples of each other 

644 m = Matrix([s.args, o.args]) 

645 return m.rank() < 2 

646 

647 @property 

648 def is_zero(self): 

649 """True if every coordinate is zero, False if any coordinate is not zero, 

650 and None if it cannot be determined.""" 

651 nonzero = [x.is_nonzero for x in self.args] 

652 if any(nonzero): 

653 return False 

654 if any(x is None for x in nonzero): 

655 return None 

656 return True 

657 

658 @property 

659 def length(self): 

660 """ 

661 Treating a Point as a Line, this returns 0 for the length of a Point. 

662 

663 Examples 

664 ======== 

665 

666 >>> from sympy import Point 

667 >>> p = Point(0, 1) 

668 >>> p.length 

669 0 

670 """ 

671 return S.Zero 

672 

673 def midpoint(self, p): 

674 """The midpoint between self and point p. 

675 

676 Parameters 

677 ========== 

678 

679 p : Point 

680 

681 Returns 

682 ======= 

683 

684 midpoint : Point 

685 

686 See Also 

687 ======== 

688 

689 sympy.geometry.line.Segment.midpoint 

690 

691 Examples 

692 ======== 

693 

694 >>> from sympy import Point 

695 >>> p1, p2 = Point(1, 1), Point(13, 5) 

696 >>> p1.midpoint(p2) 

697 Point2D(7, 3) 

698 

699 """ 

700 s, p = Point._normalize_dimension(self, Point(p)) 

701 return Point([simplify((a + b)*S.Half) for a, b in zip(s, p)]) 

702 

703 @property 

704 def origin(self): 

705 """A point of all zeros of the same ambient dimension 

706 as the current point""" 

707 return Point([0]*len(self), evaluate=False) 

708 

709 @property 

710 def orthogonal_direction(self): 

711 """Returns a non-zero point that is orthogonal to the 

712 line containing `self` and the origin. 

713 

714 Examples 

715 ======== 

716 

717 >>> from sympy import Line, Point 

718 >>> a = Point(1, 2, 3) 

719 >>> a.orthogonal_direction 

720 Point3D(-2, 1, 0) 

721 >>> b = _ 

722 >>> Line(b, b.origin).is_perpendicular(Line(a, a.origin)) 

723 True 

724 """ 

725 dim = self.ambient_dimension 

726 # if a coordinate is zero, we can put a 1 there and zeros elsewhere 

727 if self[0].is_zero: 

728 return Point([1] + (dim - 1)*[0]) 

729 if self[1].is_zero: 

730 return Point([0,1] + (dim - 2)*[0]) 

731 # if the first two coordinates aren't zero, we can create a non-zero 

732 # orthogonal vector by swapping them, negating one, and padding with zeros 

733 return Point([-self[1], self[0]] + (dim - 2)*[0]) 

734 

735 @staticmethod 

736 def project(a, b): 

737 """Project the point `a` onto the line between the origin 

738 and point `b` along the normal direction. 

739 

740 Parameters 

741 ========== 

742 

743 a : Point 

744 b : Point 

745 

746 Returns 

747 ======= 

748 

749 p : Point 

750 

751 See Also 

752 ======== 

753 

754 sympy.geometry.line.LinearEntity.projection 

755 

756 Examples 

757 ======== 

758 

759 >>> from sympy import Line, Point 

760 >>> a = Point(1, 2) 

761 >>> b = Point(2, 5) 

762 >>> z = a.origin 

763 >>> p = Point.project(a, b) 

764 >>> Line(p, a).is_perpendicular(Line(p, b)) 

765 True 

766 >>> Point.is_collinear(z, p, b) 

767 True 

768 """ 

769 a, b = Point._normalize_dimension(Point(a), Point(b)) 

770 if b.is_zero: 

771 raise ValueError("Cannot project to the zero vector.") 

772 return b*(a.dot(b) / b.dot(b)) 

773 

774 def taxicab_distance(self, p): 

775 """The Taxicab Distance from self to point p. 

776 

777 Returns the sum of the horizontal and vertical distances to point p. 

778 

779 Parameters 

780 ========== 

781 

782 p : Point 

783 

784 Returns 

785 ======= 

786 

787 taxicab_distance : The sum of the horizontal 

788 and vertical distances to point p. 

789 

790 See Also 

791 ======== 

792 

793 sympy.geometry.point.Point.distance 

794 

795 Examples 

796 ======== 

797 

798 >>> from sympy import Point 

799 >>> p1, p2 = Point(1, 1), Point(4, 5) 

800 >>> p1.taxicab_distance(p2) 

801 7 

802 

803 """ 

804 s, p = Point._normalize_dimension(self, Point(p)) 

805 return Add(*(abs(a - b) for a, b in zip(s, p))) 

806 

807 def canberra_distance(self, p): 

808 """The Canberra Distance from self to point p. 

809 

810 Returns the weighted sum of horizontal and vertical distances to 

811 point p. 

812 

813 Parameters 

814 ========== 

815 

816 p : Point 

817 

818 Returns 

819 ======= 

820 

821 canberra_distance : The weighted sum of horizontal and vertical 

822 distances to point p. The weight used is the sum of absolute values 

823 of the coordinates. 

824 

825 Examples 

826 ======== 

827 

828 >>> from sympy import Point 

829 >>> p1, p2 = Point(1, 1), Point(3, 3) 

830 >>> p1.canberra_distance(p2) 

831 1 

832 >>> p1, p2 = Point(0, 0), Point(3, 3) 

833 >>> p1.canberra_distance(p2) 

834 2 

835 

836 Raises 

837 ====== 

838 

839 ValueError when both vectors are zero. 

840 

841 See Also 

842 ======== 

843 

844 sympy.geometry.point.Point.distance 

845 

846 """ 

847 

848 s, p = Point._normalize_dimension(self, Point(p)) 

849 if self.is_zero and p.is_zero: 

850 raise ValueError("Cannot project to the zero vector.") 

851 return Add(*((abs(a - b)/(abs(a) + abs(b))) for a, b in zip(s, p))) 

852 

853 @property 

854 def unit(self): 

855 """Return the Point that is in the same direction as `self` 

856 and a distance of 1 from the origin""" 

857 return self / abs(self) 

858 

859 

860class Point2D(Point): 

861 """A point in a 2-dimensional Euclidean space. 

862 

863 Parameters 

864 ========== 

865 

866 coords 

867 A sequence of 2 coordinate values. 

868 

869 Attributes 

870 ========== 

871 

872 x 

873 y 

874 length 

875 

876 Raises 

877 ====== 

878 

879 TypeError 

880 When trying to add or subtract points with different dimensions. 

881 When trying to create a point with more than two dimensions. 

882 When `intersection` is called with object other than a Point. 

883 

884 See Also 

885 ======== 

886 

887 sympy.geometry.line.Segment : Connects two Points 

888 

889 Examples 

890 ======== 

891 

892 >>> from sympy import Point2D 

893 >>> from sympy.abc import x 

894 >>> Point2D(1, 2) 

895 Point2D(1, 2) 

896 >>> Point2D([1, 2]) 

897 Point2D(1, 2) 

898 >>> Point2D(0, x) 

899 Point2D(0, x) 

900 

901 Floats are automatically converted to Rational unless the 

902 evaluate flag is False: 

903 

904 >>> Point2D(0.5, 0.25) 

905 Point2D(1/2, 1/4) 

906 >>> Point2D(0.5, 0.25, evaluate=False) 

907 Point2D(0.5, 0.25) 

908 

909 """ 

910 

911 _ambient_dimension = 2 

912 

913 def __new__(cls, *args, _nocheck=False, **kwargs): 

914 if not _nocheck: 

915 kwargs['dim'] = 2 

916 args = Point(*args, **kwargs) 

917 return GeometryEntity.__new__(cls, *args) 

918 

919 def __contains__(self, item): 

920 return item == self 

921 

922 @property 

923 def bounds(self): 

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

925 rectangle for the geometric figure. 

926 

927 """ 

928 

929 return (self.x, self.y, self.x, self.y) 

930 

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

932 """Rotate ``angle`` radians counterclockwise about Point ``pt``. 

933 

934 See Also 

935 ======== 

936 

937 translate, scale 

938 

939 Examples 

940 ======== 

941 

942 >>> from sympy import Point2D, pi 

943 >>> t = Point2D(1, 0) 

944 >>> t.rotate(pi/2) 

945 Point2D(0, 1) 

946 >>> t.rotate(pi/2, (2, 0)) 

947 Point2D(2, -1) 

948 

949 """ 

950 c = cos(angle) 

951 s = sin(angle) 

952 

953 rv = self 

954 if pt is not None: 

955 pt = Point(pt, dim=2) 

956 rv -= pt 

957 x, y = rv.args 

958 rv = Point(c*x - s*y, s*x + c*y) 

959 if pt is not None: 

960 rv += pt 

961 return rv 

962 

963 def scale(self, x=1, y=1, pt=None): 

964 """Scale the coordinates of the Point by multiplying by 

965 ``x`` and ``y`` after subtracting ``pt`` -- default is (0, 0) -- 

966 and then adding ``pt`` back again (i.e. ``pt`` is the point of 

967 reference for the scaling). 

968 

969 See Also 

970 ======== 

971 

972 rotate, translate 

973 

974 Examples 

975 ======== 

976 

977 >>> from sympy import Point2D 

978 >>> t = Point2D(1, 1) 

979 >>> t.scale(2) 

980 Point2D(2, 1) 

981 >>> t.scale(2, 2) 

982 Point2D(2, 2) 

983 

984 """ 

985 if pt: 

986 pt = Point(pt, dim=2) 

987 return self.translate(*(-pt).args).scale(x, y).translate(*pt.args) 

988 return Point(self.x*x, self.y*y) 

989 

990 def transform(self, matrix): 

991 """Return the point after applying the transformation described 

992 by the 3x3 Matrix, ``matrix``. 

993 

994 See Also 

995 ======== 

996 sympy.geometry.point.Point2D.rotate 

997 sympy.geometry.point.Point2D.scale 

998 sympy.geometry.point.Point2D.translate 

999 """ 

1000 if not (matrix.is_Matrix and matrix.shape == (3, 3)): 

1001 raise ValueError("matrix must be a 3x3 matrix") 

1002 x, y = self.args 

1003 return Point(*(Matrix(1, 3, [x, y, 1])*matrix).tolist()[0][:2]) 

1004 

1005 def translate(self, x=0, y=0): 

1006 """Shift the Point by adding x and y to the coordinates of the Point. 

1007 

1008 See Also 

1009 ======== 

1010 

1011 sympy.geometry.point.Point2D.rotate, scale 

1012 

1013 Examples 

1014 ======== 

1015 

1016 >>> from sympy import Point2D 

1017 >>> t = Point2D(0, 1) 

1018 >>> t.translate(2) 

1019 Point2D(2, 1) 

1020 >>> t.translate(2, 2) 

1021 Point2D(2, 3) 

1022 >>> t + Point2D(2, 2) 

1023 Point2D(2, 3) 

1024 

1025 """ 

1026 return Point(self.x + x, self.y + y) 

1027 

1028 @property 

1029 def coordinates(self): 

1030 """ 

1031 Returns the two coordinates of the Point. 

1032 

1033 Examples 

1034 ======== 

1035 

1036 >>> from sympy import Point2D 

1037 >>> p = Point2D(0, 1) 

1038 >>> p.coordinates 

1039 (0, 1) 

1040 """ 

1041 return self.args 

1042 

1043 @property 

1044 def x(self): 

1045 """ 

1046 Returns the X coordinate of the Point. 

1047 

1048 Examples 

1049 ======== 

1050 

1051 >>> from sympy import Point2D 

1052 >>> p = Point2D(0, 1) 

1053 >>> p.x 

1054 0 

1055 """ 

1056 return self.args[0] 

1057 

1058 @property 

1059 def y(self): 

1060 """ 

1061 Returns the Y coordinate of the Point. 

1062 

1063 Examples 

1064 ======== 

1065 

1066 >>> from sympy import Point2D 

1067 >>> p = Point2D(0, 1) 

1068 >>> p.y 

1069 1 

1070 """ 

1071 return self.args[1] 

1072 

1073class Point3D(Point): 

1074 """A point in a 3-dimensional Euclidean space. 

1075 

1076 Parameters 

1077 ========== 

1078 

1079 coords 

1080 A sequence of 3 coordinate values. 

1081 

1082 Attributes 

1083 ========== 

1084 

1085 x 

1086 y 

1087 z 

1088 length 

1089 

1090 Raises 

1091 ====== 

1092 

1093 TypeError 

1094 When trying to add or subtract points with different dimensions. 

1095 When `intersection` is called with object other than a Point. 

1096 

1097 Examples 

1098 ======== 

1099 

1100 >>> from sympy import Point3D 

1101 >>> from sympy.abc import x 

1102 >>> Point3D(1, 2, 3) 

1103 Point3D(1, 2, 3) 

1104 >>> Point3D([1, 2, 3]) 

1105 Point3D(1, 2, 3) 

1106 >>> Point3D(0, x, 3) 

1107 Point3D(0, x, 3) 

1108 

1109 Floats are automatically converted to Rational unless the 

1110 evaluate flag is False: 

1111 

1112 >>> Point3D(0.5, 0.25, 2) 

1113 Point3D(1/2, 1/4, 2) 

1114 >>> Point3D(0.5, 0.25, 3, evaluate=False) 

1115 Point3D(0.5, 0.25, 3) 

1116 

1117 """ 

1118 

1119 _ambient_dimension = 3 

1120 

1121 def __new__(cls, *args, _nocheck=False, **kwargs): 

1122 if not _nocheck: 

1123 kwargs['dim'] = 3 

1124 args = Point(*args, **kwargs) 

1125 return GeometryEntity.__new__(cls, *args) 

1126 

1127 def __contains__(self, item): 

1128 return item == self 

1129 

1130 @staticmethod 

1131 def are_collinear(*points): 

1132 """Is a sequence of points collinear? 

1133 

1134 Test whether or not a set of points are collinear. Returns True if 

1135 the set of points are collinear, or False otherwise. 

1136 

1137 Parameters 

1138 ========== 

1139 

1140 points : sequence of Point 

1141 

1142 Returns 

1143 ======= 

1144 

1145 are_collinear : boolean 

1146 

1147 See Also 

1148 ======== 

1149 

1150 sympy.geometry.line.Line3D 

1151 

1152 Examples 

1153 ======== 

1154 

1155 >>> from sympy import Point3D 

1156 >>> from sympy.abc import x 

1157 >>> p1, p2 = Point3D(0, 0, 0), Point3D(1, 1, 1) 

1158 >>> p3, p4, p5 = Point3D(2, 2, 2), Point3D(x, x, x), Point3D(1, 2, 6) 

1159 >>> Point3D.are_collinear(p1, p2, p3, p4) 

1160 True 

1161 >>> Point3D.are_collinear(p1, p2, p3, p5) 

1162 False 

1163 """ 

1164 return Point.is_collinear(*points) 

1165 

1166 def direction_cosine(self, point): 

1167 """ 

1168 Gives the direction cosine between 2 points 

1169 

1170 Parameters 

1171 ========== 

1172 

1173 p : Point3D 

1174 

1175 Returns 

1176 ======= 

1177 

1178 list 

1179 

1180 Examples 

1181 ======== 

1182 

1183 >>> from sympy import Point3D 

1184 >>> p1 = Point3D(1, 2, 3) 

1185 >>> p1.direction_cosine(Point3D(2, 3, 5)) 

1186 [sqrt(6)/6, sqrt(6)/6, sqrt(6)/3] 

1187 """ 

1188 a = self.direction_ratio(point) 

1189 b = sqrt(Add(*(i**2 for i in a))) 

1190 return [(point.x - self.x) / b,(point.y - self.y) / b, 

1191 (point.z - self.z) / b] 

1192 

1193 def direction_ratio(self, point): 

1194 """ 

1195 Gives the direction ratio between 2 points 

1196 

1197 Parameters 

1198 ========== 

1199 

1200 p : Point3D 

1201 

1202 Returns 

1203 ======= 

1204 

1205 list 

1206 

1207 Examples 

1208 ======== 

1209 

1210 >>> from sympy import Point3D 

1211 >>> p1 = Point3D(1, 2, 3) 

1212 >>> p1.direction_ratio(Point3D(2, 3, 5)) 

1213 [1, 1, 2] 

1214 """ 

1215 return [(point.x - self.x),(point.y - self.y),(point.z - self.z)] 

1216 

1217 def intersection(self, other): 

1218 """The intersection between this point and another GeometryEntity. 

1219 

1220 Parameters 

1221 ========== 

1222 

1223 other : GeometryEntity or sequence of coordinates 

1224 

1225 Returns 

1226 ======= 

1227 

1228 intersection : list of Points 

1229 

1230 Notes 

1231 ===== 

1232 

1233 The return value will either be an empty list if there is no 

1234 intersection, otherwise it will contain this point. 

1235 

1236 Examples 

1237 ======== 

1238 

1239 >>> from sympy import Point3D 

1240 >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(1, 1, 1), Point3D(0, 0, 0) 

1241 >>> p1.intersection(p2) 

1242 [] 

1243 >>> p1.intersection(p3) 

1244 [Point3D(0, 0, 0)] 

1245 

1246 """ 

1247 if not isinstance(other, GeometryEntity): 

1248 other = Point(other, dim=3) 

1249 if isinstance(other, Point3D): 

1250 if self == other: 

1251 return [self] 

1252 return [] 

1253 return other.intersection(self) 

1254 

1255 def scale(self, x=1, y=1, z=1, pt=None): 

1256 """Scale the coordinates of the Point by multiplying by 

1257 ``x`` and ``y`` after subtracting ``pt`` -- default is (0, 0) -- 

1258 and then adding ``pt`` back again (i.e. ``pt`` is the point of 

1259 reference for the scaling). 

1260 

1261 See Also 

1262 ======== 

1263 

1264 translate 

1265 

1266 Examples 

1267 ======== 

1268 

1269 >>> from sympy import Point3D 

1270 >>> t = Point3D(1, 1, 1) 

1271 >>> t.scale(2) 

1272 Point3D(2, 1, 1) 

1273 >>> t.scale(2, 2) 

1274 Point3D(2, 2, 1) 

1275 

1276 """ 

1277 if pt: 

1278 pt = Point3D(pt) 

1279 return self.translate(*(-pt).args).scale(x, y, z).translate(*pt.args) 

1280 return Point3D(self.x*x, self.y*y, self.z*z) 

1281 

1282 def transform(self, matrix): 

1283 """Return the point after applying the transformation described 

1284 by the 4x4 Matrix, ``matrix``. 

1285 

1286 See Also 

1287 ======== 

1288 sympy.geometry.point.Point3D.scale 

1289 sympy.geometry.point.Point3D.translate 

1290 """ 

1291 if not (matrix.is_Matrix and matrix.shape == (4, 4)): 

1292 raise ValueError("matrix must be a 4x4 matrix") 

1293 x, y, z = self.args 

1294 m = Transpose(matrix) 

1295 return Point3D(*(Matrix(1, 4, [x, y, z, 1])*m).tolist()[0][:3]) 

1296 

1297 def translate(self, x=0, y=0, z=0): 

1298 """Shift the Point by adding x and y to the coordinates of the Point. 

1299 

1300 See Also 

1301 ======== 

1302 

1303 scale 

1304 

1305 Examples 

1306 ======== 

1307 

1308 >>> from sympy import Point3D 

1309 >>> t = Point3D(0, 1, 1) 

1310 >>> t.translate(2) 

1311 Point3D(2, 1, 1) 

1312 >>> t.translate(2, 2) 

1313 Point3D(2, 3, 1) 

1314 >>> t + Point3D(2, 2, 2) 

1315 Point3D(2, 3, 3) 

1316 

1317 """ 

1318 return Point3D(self.x + x, self.y + y, self.z + z) 

1319 

1320 @property 

1321 def coordinates(self): 

1322 """ 

1323 Returns the three coordinates of the Point. 

1324 

1325 Examples 

1326 ======== 

1327 

1328 >>> from sympy import Point3D 

1329 >>> p = Point3D(0, 1, 2) 

1330 >>> p.coordinates 

1331 (0, 1, 2) 

1332 """ 

1333 return self.args 

1334 

1335 @property 

1336 def x(self): 

1337 """ 

1338 Returns the X coordinate of the Point. 

1339 

1340 Examples 

1341 ======== 

1342 

1343 >>> from sympy import Point3D 

1344 >>> p = Point3D(0, 1, 3) 

1345 >>> p.x 

1346 0 

1347 """ 

1348 return self.args[0] 

1349 

1350 @property 

1351 def y(self): 

1352 """ 

1353 Returns the Y coordinate of the Point. 

1354 

1355 Examples 

1356 ======== 

1357 

1358 >>> from sympy import Point3D 

1359 >>> p = Point3D(0, 1, 2) 

1360 >>> p.y 

1361 1 

1362 """ 

1363 return self.args[1] 

1364 

1365 @property 

1366 def z(self): 

1367 """ 

1368 Returns the Z coordinate of the Point. 

1369 

1370 Examples 

1371 ======== 

1372 

1373 >>> from sympy import Point3D 

1374 >>> p = Point3D(0, 1, 1) 

1375 >>> p.z 

1376 1 

1377 """ 

1378 return self.args[2]