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
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
1"""Geometrical Points.
3Contains
4========
5Point
6Point2D
7Point3D
9When methods of Point require 1 or more points as arguments, they
10can be passed as a sequence of coordinates or Points:
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
18"""
20import warnings
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
37from .entity import GeometryEntity
39from mpmath.libmp.libmpf import prec_to_dps
42class Point(GeometryEntity):
43 """A point in a n-dimensional Euclidean space.
45 Parameters
46 ==========
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.
63 Attributes
64 ==========
66 length
67 origin: A `Point` representing the origin of the
68 appropriately-dimensioned space.
70 Raises
71 ======
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.
78 See Also
79 ========
81 sympy.geometry.line.Segment : Connects two Points
83 Examples
84 ========
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)
97 Floats are automatically converted to Rational unless the
98 evaluate flag is False:
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)
105 """
107 is_Point = True
109 def __new__(cls, *args, **kwargs):
110 evaluate = kwargs.get('evaluate', global_parameters.evaluate)
111 on_morph = kwargs.get('on_morph', 'ignore')
113 # unpack into coords
114 coords = args[0] if len(args) == 1 else args
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
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')
133 coords = Tuple(*coords)
134 dim = kwargs.get('dim', len(coords))
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.')
160 # pad with zeros appropriately
161 coords = coords[:dim] + (S.Zero,)*(dim - len(coords))
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)})
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)
178 # the general Point
179 return GeometryEntity.__new__(cls, *coords)
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)
186 def __add__(self, other):
187 """Add other to self by incrementing self's coordinates by
188 those of other.
190 Notes
191 =====
193 >>> from sympy import Point
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.
200 >>> Point(1, 2) + (.1, .2)
201 Point2D(1.1, 2.2)
203 If this is not desired, the `translate` method can be used or
204 another Point can be added:
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)
211 See Also
212 ========
214 sympy.geometry.point.Point.translate
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))
222 coords = [simplify(a + b) for a, b in zip(s, o)]
223 return Point(coords, evaluate=False)
225 def __contains__(self, item):
226 return item in self.args
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)
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
239 def __getitem__(self, key):
240 return self.args[key]
242 def __hash__(self):
243 return hash(self.args)
245 def __iter__(self):
246 return self.args.__iter__()
248 def __len__(self):
249 return len(self.args)
251 def __mul__(self, factor):
252 """Multiply point's coordinates by a factor.
254 Notes
255 =====
257 >>> from sympy import Point
259 When multiplying a Point by a floating point number,
260 the coordinates of the Point will be changed to Floats:
262 >>> Point(1, 2)*0.1
263 Point2D(0.1, 0.2)
265 If this is not desired, the `scale` method can be used or
266 else only multiply or divide by integers:
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)
273 See Also
274 ========
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)
282 def __rmul__(self, factor):
283 """Multiply a factor by point's coordinates."""
284 return self.__mul__(factor)
286 def __neg__(self):
287 """Negate the point."""
288 coords = [-x for x in self.args]
289 return Point(coords, evaluate=False)
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]
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]
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."""
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:]]
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)
336 @property
337 def ambient_dimension(self):
338 """Number of components this point has."""
339 return getattr(self, '_ambient_dimension', len(self))
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.
347 Parameters
348 ==========
350 A set of points
352 Raises
353 ======
355 ValueError : if less than 3 unique points are given
357 Returns
358 =======
360 boolean
362 Examples
363 ========
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
376 """
377 if len(points) <= 1:
378 return True
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
387 def distance(self, other):
388 """The Euclidean distance between self and another GeometricEntity.
390 Returns
391 =======
393 distance : number or symbolic expression.
395 Raises
396 ======
398 TypeError : if other is not recognized as a GeometricEntity or is a
399 GeometricEntity for which distance is not defined.
401 See Also
402 ========
404 sympy.geometry.line.Segment.length
405 sympy.geometry.point.Point.taxicab_distance
407 Examples
408 ========
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)
418 The computed distance may be symbolic, too:
420 >>> from sympy.abc import x, y
421 >>> p3 = Point(x, y)
422 >>> p3.distance((0, 0))
423 sqrt(x**2 + y**2)
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)
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)))
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))
452 def _eval_evalf(self, prec=15, **options):
453 """Evaluate the coordinates of the point.
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).
459 Parameters
460 ==========
462 prec : int
464 Returns
465 =======
467 point : Point
469 Examples
470 ========
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)
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)
484 def intersection(self, other):
485 """The intersection between this point and another GeometryEntity.
487 Parameters
488 ==========
490 other : GeometryEntity or sequence of coordinates
492 Returns
493 =======
495 intersection : list of Points
497 Notes
498 =====
500 The return value will either be an empty list if there is no
501 intersection, otherwise it will contain this point.
503 Examples
504 ========
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)]
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)
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.
530 Parameters
531 ==========
533 args : sequence of Points
535 Returns
536 =======
538 is_collinear : boolean
540 See Also
541 ========
543 sympy.geometry.line.Line
545 Examples
546 ========
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
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
563 def is_concyclic(self, *args):
564 """Do `self` and the given sequence of points lie in a circle?
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.
570 Parameters
571 ==========
573 args : sequence of Points
575 Returns
576 =======
578 is_concyclic : boolean
581 Examples
582 ========
584 >>> from sympy import Point
586 Define 4 points that are on the unit circle:
588 >>> p1, p2, p3, p4 = Point(1, 0), (0, 1), (-1, 0), (0, -1)
590 >>> p1.is_concyclic() == p1.is_concyclic(p2, p3, p4) == True
591 True
593 Define a point not on that circle:
595 >>> p = Point(1, 1)
597 >>> p.is_concyclic(p1, p2, p3)
598 False
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
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
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)))
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
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
658 @property
659 def length(self):
660 """
661 Treating a Point as a Line, this returns 0 for the length of a Point.
663 Examples
664 ========
666 >>> from sympy import Point
667 >>> p = Point(0, 1)
668 >>> p.length
669 0
670 """
671 return S.Zero
673 def midpoint(self, p):
674 """The midpoint between self and point p.
676 Parameters
677 ==========
679 p : Point
681 Returns
682 =======
684 midpoint : Point
686 See Also
687 ========
689 sympy.geometry.line.Segment.midpoint
691 Examples
692 ========
694 >>> from sympy import Point
695 >>> p1, p2 = Point(1, 1), Point(13, 5)
696 >>> p1.midpoint(p2)
697 Point2D(7, 3)
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)])
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)
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.
714 Examples
715 ========
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])
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.
740 Parameters
741 ==========
743 a : Point
744 b : Point
746 Returns
747 =======
749 p : Point
751 See Also
752 ========
754 sympy.geometry.line.LinearEntity.projection
756 Examples
757 ========
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))
774 def taxicab_distance(self, p):
775 """The Taxicab Distance from self to point p.
777 Returns the sum of the horizontal and vertical distances to point p.
779 Parameters
780 ==========
782 p : Point
784 Returns
785 =======
787 taxicab_distance : The sum of the horizontal
788 and vertical distances to point p.
790 See Also
791 ========
793 sympy.geometry.point.Point.distance
795 Examples
796 ========
798 >>> from sympy import Point
799 >>> p1, p2 = Point(1, 1), Point(4, 5)
800 >>> p1.taxicab_distance(p2)
801 7
803 """
804 s, p = Point._normalize_dimension(self, Point(p))
805 return Add(*(abs(a - b) for a, b in zip(s, p)))
807 def canberra_distance(self, p):
808 """The Canberra Distance from self to point p.
810 Returns the weighted sum of horizontal and vertical distances to
811 point p.
813 Parameters
814 ==========
816 p : Point
818 Returns
819 =======
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.
825 Examples
826 ========
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
836 Raises
837 ======
839 ValueError when both vectors are zero.
841 See Also
842 ========
844 sympy.geometry.point.Point.distance
846 """
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)))
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)
860class Point2D(Point):
861 """A point in a 2-dimensional Euclidean space.
863 Parameters
864 ==========
866 coords
867 A sequence of 2 coordinate values.
869 Attributes
870 ==========
872 x
873 y
874 length
876 Raises
877 ======
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.
884 See Also
885 ========
887 sympy.geometry.line.Segment : Connects two Points
889 Examples
890 ========
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)
901 Floats are automatically converted to Rational unless the
902 evaluate flag is False:
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)
909 """
911 _ambient_dimension = 2
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)
919 def __contains__(self, item):
920 return item == self
922 @property
923 def bounds(self):
924 """Return a tuple (xmin, ymin, xmax, ymax) representing the bounding
925 rectangle for the geometric figure.
927 """
929 return (self.x, self.y, self.x, self.y)
931 def rotate(self, angle, pt=None):
932 """Rotate ``angle`` radians counterclockwise about Point ``pt``.
934 See Also
935 ========
937 translate, scale
939 Examples
940 ========
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)
949 """
950 c = cos(angle)
951 s = sin(angle)
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
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).
969 See Also
970 ========
972 rotate, translate
974 Examples
975 ========
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)
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)
990 def transform(self, matrix):
991 """Return the point after applying the transformation described
992 by the 3x3 Matrix, ``matrix``.
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])
1005 def translate(self, x=0, y=0):
1006 """Shift the Point by adding x and y to the coordinates of the Point.
1008 See Also
1009 ========
1011 sympy.geometry.point.Point2D.rotate, scale
1013 Examples
1014 ========
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)
1025 """
1026 return Point(self.x + x, self.y + y)
1028 @property
1029 def coordinates(self):
1030 """
1031 Returns the two coordinates of the Point.
1033 Examples
1034 ========
1036 >>> from sympy import Point2D
1037 >>> p = Point2D(0, 1)
1038 >>> p.coordinates
1039 (0, 1)
1040 """
1041 return self.args
1043 @property
1044 def x(self):
1045 """
1046 Returns the X coordinate of the Point.
1048 Examples
1049 ========
1051 >>> from sympy import Point2D
1052 >>> p = Point2D(0, 1)
1053 >>> p.x
1054 0
1055 """
1056 return self.args[0]
1058 @property
1059 def y(self):
1060 """
1061 Returns the Y coordinate of the Point.
1063 Examples
1064 ========
1066 >>> from sympy import Point2D
1067 >>> p = Point2D(0, 1)
1068 >>> p.y
1069 1
1070 """
1071 return self.args[1]
1073class Point3D(Point):
1074 """A point in a 3-dimensional Euclidean space.
1076 Parameters
1077 ==========
1079 coords
1080 A sequence of 3 coordinate values.
1082 Attributes
1083 ==========
1085 x
1086 y
1087 z
1088 length
1090 Raises
1091 ======
1093 TypeError
1094 When trying to add or subtract points with different dimensions.
1095 When `intersection` is called with object other than a Point.
1097 Examples
1098 ========
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)
1109 Floats are automatically converted to Rational unless the
1110 evaluate flag is False:
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)
1117 """
1119 _ambient_dimension = 3
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)
1127 def __contains__(self, item):
1128 return item == self
1130 @staticmethod
1131 def are_collinear(*points):
1132 """Is a sequence of points collinear?
1134 Test whether or not a set of points are collinear. Returns True if
1135 the set of points are collinear, or False otherwise.
1137 Parameters
1138 ==========
1140 points : sequence of Point
1142 Returns
1143 =======
1145 are_collinear : boolean
1147 See Also
1148 ========
1150 sympy.geometry.line.Line3D
1152 Examples
1153 ========
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)
1166 def direction_cosine(self, point):
1167 """
1168 Gives the direction cosine between 2 points
1170 Parameters
1171 ==========
1173 p : Point3D
1175 Returns
1176 =======
1178 list
1180 Examples
1181 ========
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]
1193 def direction_ratio(self, point):
1194 """
1195 Gives the direction ratio between 2 points
1197 Parameters
1198 ==========
1200 p : Point3D
1202 Returns
1203 =======
1205 list
1207 Examples
1208 ========
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)]
1217 def intersection(self, other):
1218 """The intersection between this point and another GeometryEntity.
1220 Parameters
1221 ==========
1223 other : GeometryEntity or sequence of coordinates
1225 Returns
1226 =======
1228 intersection : list of Points
1230 Notes
1231 =====
1233 The return value will either be an empty list if there is no
1234 intersection, otherwise it will contain this point.
1236 Examples
1237 ========
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)]
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)
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).
1261 See Also
1262 ========
1264 translate
1266 Examples
1267 ========
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)
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)
1282 def transform(self, matrix):
1283 """Return the point after applying the transformation described
1284 by the 4x4 Matrix, ``matrix``.
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])
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.
1300 See Also
1301 ========
1303 scale
1305 Examples
1306 ========
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)
1317 """
1318 return Point3D(self.x + x, self.y + y, self.z + z)
1320 @property
1321 def coordinates(self):
1322 """
1323 Returns the three coordinates of the Point.
1325 Examples
1326 ========
1328 >>> from sympy import Point3D
1329 >>> p = Point3D(0, 1, 2)
1330 >>> p.coordinates
1331 (0, 1, 2)
1332 """
1333 return self.args
1335 @property
1336 def x(self):
1337 """
1338 Returns the X coordinate of the Point.
1340 Examples
1341 ========
1343 >>> from sympy import Point3D
1344 >>> p = Point3D(0, 1, 3)
1345 >>> p.x
1346 0
1347 """
1348 return self.args[0]
1350 @property
1351 def y(self):
1352 """
1353 Returns the Y coordinate of the Point.
1355 Examples
1356 ========
1358 >>> from sympy import Point3D
1359 >>> p = Point3D(0, 1, 2)
1360 >>> p.y
1361 1
1362 """
1363 return self.args[1]
1365 @property
1366 def z(self):
1367 """
1368 Returns the Z coordinate of the Point.
1370 Examples
1371 ========
1373 >>> from sympy import Point3D
1374 >>> p = Point3D(0, 1, 1)
1375 >>> p.z
1376 1
1377 """
1378 return self.args[2]