12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376 |
- """Geometrical Points.
- Contains
- ========
- Point
- Point2D
- Point3D
- When methods of Point require 1 or more points as arguments, they
- can be passed as a sequence of coordinates or Points:
- >>> from sympy import Point
- >>> Point(1, 1).is_collinear((2, 2), (3, 4))
- False
- >>> Point(1, 1).is_collinear(Point(2, 2), Point(3, 4))
- False
- """
- import warnings
- from sympy.core import S, sympify, Expr
- from sympy.core.add import Add
- from sympy.core.containers import Tuple
- from sympy.core.numbers import Float
- from sympy.core.parameters import global_parameters
- from sympy.simplify import nsimplify, simplify
- from sympy.geometry.exceptions import GeometryError
- from sympy.functions.elementary.miscellaneous import sqrt
- from sympy.functions.elementary.complexes import im
- from sympy.functions.elementary.trigonometric import cos, sin
- from sympy.matrices import Matrix
- from sympy.matrices.expressions import Transpose
- from sympy.utilities.iterables import uniq, is_sequence
- from sympy.utilities.misc import filldedent, func_name, Undecidable
- from .entity import GeometryEntity
- from mpmath.libmp.libmpf import prec_to_dps
- class Point(GeometryEntity):
- """A point in a n-dimensional Euclidean space.
- Parameters
- ==========
- coords : sequence of n-coordinate values. In the special
- case where n=2 or 3, a Point2D or Point3D will be created
- as appropriate.
- evaluate : if `True` (default), all floats are turn into
- exact types.
- dim : number of coordinates the point should have. If coordinates
- are unspecified, they are padded with zeros.
- on_morph : indicates what should happen when the number of
- coordinates of a point need to be changed by adding or
- removing zeros. Possible values are `'warn'`, `'error'`, or
- `ignore` (default). No warning or error is given when `*args`
- is empty and `dim` is given. An error is always raised when
- trying to remove nonzero coordinates.
- Attributes
- ==========
- length
- origin: A `Point` representing the origin of the
- appropriately-dimensioned space.
- Raises
- ======
- TypeError : When instantiating with anything but a Point or sequence
- ValueError : when instantiating with a sequence with length < 2 or
- when trying to reduce dimensions if keyword `on_morph='error'` is
- set.
- See Also
- ========
- sympy.geometry.line.Segment : Connects two Points
- Examples
- ========
- >>> from sympy import Point
- >>> from sympy.abc import x
- >>> Point(1, 2, 3)
- Point3D(1, 2, 3)
- >>> Point([1, 2])
- Point2D(1, 2)
- >>> Point(0, x)
- Point2D(0, x)
- >>> Point(dim=4)
- Point(0, 0, 0, 0)
- Floats are automatically converted to Rational unless the
- evaluate flag is False:
- >>> Point(0.5, 0.25)
- Point2D(1/2, 1/4)
- >>> Point(0.5, 0.25, evaluate=False)
- Point2D(0.5, 0.25)
- """
- is_Point = True
- def __new__(cls, *args, **kwargs):
- evaluate = kwargs.get('evaluate', global_parameters.evaluate)
- on_morph = kwargs.get('on_morph', 'ignore')
- # unpack into coords
- coords = args[0] if len(args) == 1 else args
- # check args and handle quickly handle Point instances
- if isinstance(coords, Point):
- # even if we're mutating the dimension of a point, we
- # don't reevaluate its coordinates
- evaluate = False
- if len(coords) == kwargs.get('dim', len(coords)):
- return coords
- if not is_sequence(coords):
- raise TypeError(filldedent('''
- Expecting sequence of coordinates, not `{}`'''
- .format(func_name(coords))))
- # A point where only `dim` is specified is initialized
- # to zeros.
- if len(coords) == 0 and kwargs.get('dim', None):
- coords = (S.Zero,)*kwargs.get('dim')
- coords = Tuple(*coords)
- dim = kwargs.get('dim', len(coords))
- if len(coords) < 2:
- raise ValueError(filldedent('''
- Point requires 2 or more coordinates or
- keyword `dim` > 1.'''))
- if len(coords) != dim:
- message = ("Dimension of {} needs to be changed "
- "from {} to {}.").format(coords, len(coords), dim)
- if on_morph == 'ignore':
- pass
- elif on_morph == "error":
- raise ValueError(message)
- elif on_morph == 'warn':
- warnings.warn(message, stacklevel=2)
- else:
- raise ValueError(filldedent('''
- on_morph value should be 'error',
- 'warn' or 'ignore'.'''))
- if any(coords[dim:]):
- raise ValueError('Nonzero coordinates cannot be removed.')
- if any(a.is_number and im(a).is_zero is False for a in coords):
- raise ValueError('Imaginary coordinates are not permitted.')
- if not all(isinstance(a, Expr) for a in coords):
- raise TypeError('Coordinates must be valid SymPy expressions.')
- # pad with zeros appropriately
- coords = coords[:dim] + (S.Zero,)*(dim - len(coords))
- # Turn any Floats into rationals and simplify
- # any expressions before we instantiate
- if evaluate:
- coords = coords.xreplace({
- f: simplify(nsimplify(f, rational=True))
- for f in coords.atoms(Float)})
- # return 2D or 3D instances
- if len(coords) == 2:
- kwargs['_nocheck'] = True
- return Point2D(*coords, **kwargs)
- elif len(coords) == 3:
- kwargs['_nocheck'] = True
- return Point3D(*coords, **kwargs)
- # the general Point
- return GeometryEntity.__new__(cls, *coords)
- def __abs__(self):
- """Returns the distance between this point and the origin."""
- origin = Point([0]*len(self))
- return Point.distance(origin, self)
- def __add__(self, other):
- """Add other to self by incrementing self's coordinates by
- those of other.
- Notes
- =====
- >>> from sympy import Point
- When sequences of coordinates are passed to Point methods, they
- are converted to a Point internally. This __add__ method does
- not do that so if floating point values are used, a floating
- point result (in terms of SymPy Floats) will be returned.
- >>> Point(1, 2) + (.1, .2)
- Point2D(1.1, 2.2)
- If this is not desired, the `translate` method can be used or
- another Point can be added:
- >>> Point(1, 2).translate(.1, .2)
- Point2D(11/10, 11/5)
- >>> Point(1, 2) + Point(.1, .2)
- Point2D(11/10, 11/5)
- See Also
- ========
- sympy.geometry.point.Point.translate
- """
- try:
- s, o = Point._normalize_dimension(self, Point(other, evaluate=False))
- except TypeError:
- raise GeometryError("Don't know how to add {} and a Point object".format(other))
- coords = [simplify(a + b) for a, b in zip(s, o)]
- return Point(coords, evaluate=False)
- def __contains__(self, item):
- return item in self.args
- def __truediv__(self, divisor):
- """Divide point's coordinates by a factor."""
- divisor = sympify(divisor)
- coords = [simplify(x/divisor) for x in self.args]
- return Point(coords, evaluate=False)
- def __eq__(self, other):
- if not isinstance(other, Point) or len(self.args) != len(other.args):
- return False
- return self.args == other.args
- def __getitem__(self, key):
- return self.args[key]
- def __hash__(self):
- return hash(self.args)
- def __iter__(self):
- return self.args.__iter__()
- def __len__(self):
- return len(self.args)
- def __mul__(self, factor):
- """Multiply point's coordinates by a factor.
- Notes
- =====
- >>> from sympy import Point
- When multiplying a Point by a floating point number,
- the coordinates of the Point will be changed to Floats:
- >>> Point(1, 2)*0.1
- Point2D(0.1, 0.2)
- If this is not desired, the `scale` method can be used or
- else only multiply or divide by integers:
- >>> Point(1, 2).scale(1.1, 1.1)
- Point2D(11/10, 11/5)
- >>> Point(1, 2)*11/10
- Point2D(11/10, 11/5)
- See Also
- ========
- sympy.geometry.point.Point.scale
- """
- factor = sympify(factor)
- coords = [simplify(x*factor) for x in self.args]
- return Point(coords, evaluate=False)
- def __rmul__(self, factor):
- """Multiply a factor by point's coordinates."""
- return self.__mul__(factor)
- def __neg__(self):
- """Negate the point."""
- coords = [-x for x in self.args]
- return Point(coords, evaluate=False)
- def __sub__(self, other):
- """Subtract two points, or subtract a factor from this point's
- coordinates."""
- return self + [-x for x in other]
- @classmethod
- def _normalize_dimension(cls, *points, **kwargs):
- """Ensure that points have the same dimension.
- By default `on_morph='warn'` is passed to the
- `Point` constructor."""
- # if we have a built-in ambient dimension, use it
- dim = getattr(cls, '_ambient_dimension', None)
- # override if we specified it
- dim = kwargs.get('dim', dim)
- # if no dim was given, use the highest dimensional point
- if dim is None:
- dim = max(i.ambient_dimension for i in points)
- if all(i.ambient_dimension == dim for i in points):
- return list(points)
- kwargs['dim'] = dim
- kwargs['on_morph'] = kwargs.get('on_morph', 'warn')
- return [Point(i, **kwargs) for i in points]
- @staticmethod
- def affine_rank(*args):
- """The affine rank of a set of points is the dimension
- of the smallest affine space containing all the points.
- For example, if the points lie on a line (and are not all
- the same) their affine rank is 1. If the points lie on a plane
- but not a line, their affine rank is 2. By convention, the empty
- set has affine rank -1."""
- if len(args) == 0:
- return -1
- # make sure we're genuinely points
- # and translate every point to the origin
- points = Point._normalize_dimension(*[Point(i) for i in args])
- origin = points[0]
- points = [i - origin for i in points[1:]]
- m = Matrix([i.args for i in points])
- # XXX fragile -- what is a better way?
- return m.rank(iszerofunc = lambda x:
- abs(x.n(2)) < 1e-12 if x.is_number else x.is_zero)
- @property
- def ambient_dimension(self):
- """Number of components this point has."""
- return getattr(self, '_ambient_dimension', len(self))
- @classmethod
- def are_coplanar(cls, *points):
- """Return True if there exists a plane in which all the points
- lie. A trivial True value is returned if `len(points) < 3` or
- all Points are 2-dimensional.
- Parameters
- ==========
- A set of points
- Raises
- ======
- ValueError : if less than 3 unique points are given
- Returns
- =======
- boolean
- Examples
- ========
- >>> from sympy import Point3D
- >>> p1 = Point3D(1, 2, 2)
- >>> p2 = Point3D(2, 7, 2)
- >>> p3 = Point3D(0, 0, 2)
- >>> p4 = Point3D(1, 1, 2)
- >>> Point3D.are_coplanar(p1, p2, p3, p4)
- True
- >>> p5 = Point3D(0, 1, 3)
- >>> Point3D.are_coplanar(p1, p2, p3, p5)
- False
- """
- if len(points) <= 1:
- return True
- points = cls._normalize_dimension(*[Point(i) for i in points])
- # quick exit if we are in 2D
- if points[0].ambient_dimension == 2:
- return True
- points = list(uniq(points))
- return Point.affine_rank(*points) <= 2
- def distance(self, other):
- """The Euclidean distance between self and another GeometricEntity.
- Returns
- =======
- distance : number or symbolic expression.
- Raises
- ======
- TypeError : if other is not recognized as a GeometricEntity or is a
- GeometricEntity for which distance is not defined.
- See Also
- ========
- sympy.geometry.line.Segment.length
- sympy.geometry.point.Point.taxicab_distance
- Examples
- ========
- >>> from sympy import Point, Line
- >>> p1, p2 = Point(1, 1), Point(4, 5)
- >>> l = Line((3, 1), (2, 2))
- >>> p1.distance(p2)
- 5
- >>> p1.distance(l)
- sqrt(2)
- The computed distance may be symbolic, too:
- >>> from sympy.abc import x, y
- >>> p3 = Point(x, y)
- >>> p3.distance((0, 0))
- sqrt(x**2 + y**2)
- """
- if not isinstance(other, GeometryEntity):
- try:
- other = Point(other, dim=self.ambient_dimension)
- except TypeError:
- raise TypeError("not recognized as a GeometricEntity: %s" % type(other))
- if isinstance(other, Point):
- s, p = Point._normalize_dimension(self, Point(other))
- return sqrt(Add(*((a - b)**2 for a, b in zip(s, p))))
- distance = getattr(other, 'distance', None)
- if distance is None:
- raise TypeError("distance between Point and %s is not defined" % type(other))
- return distance(self)
- def dot(self, p):
- """Return dot product of self with another Point."""
- if not is_sequence(p):
- p = Point(p) # raise the error via Point
- return Add(*(a*b for a, b in zip(self, p)))
- def equals(self, other):
- """Returns whether the coordinates of self and other agree."""
- # a point is equal to another point if all its components are equal
- if not isinstance(other, Point) or len(self) != len(other):
- return False
- return all(a.equals(b) for a, b in zip(self, other))
- def _eval_evalf(self, prec=15, **options):
- """Evaluate the coordinates of the point.
- This method will, where possible, create and return a new Point
- where the coordinates are evaluated as floating point numbers to
- the precision indicated (default=15).
- Parameters
- ==========
- prec : int
- Returns
- =======
- point : Point
- Examples
- ========
- >>> from sympy import Point, Rational
- >>> p1 = Point(Rational(1, 2), Rational(3, 2))
- >>> p1
- Point2D(1/2, 3/2)
- >>> p1.evalf()
- Point2D(0.5, 1.5)
- """
- dps = prec_to_dps(prec)
- coords = [x.evalf(n=dps, **options) for x in self.args]
- return Point(*coords, evaluate=False)
- def intersection(self, other):
- """The intersection between this point and another GeometryEntity.
- Parameters
- ==========
- other : GeometryEntity or sequence of coordinates
- Returns
- =======
- intersection : list of Points
- Notes
- =====
- The return value will either be an empty list if there is no
- intersection, otherwise it will contain this point.
- Examples
- ========
- >>> from sympy import Point
- >>> p1, p2, p3 = Point(0, 0), Point(1, 1), Point(0, 0)
- >>> p1.intersection(p2)
- []
- >>> p1.intersection(p3)
- [Point2D(0, 0)]
- """
- if not isinstance(other, GeometryEntity):
- other = Point(other)
- if isinstance(other, Point):
- if self == other:
- return [self]
- p1, p2 = Point._normalize_dimension(self, other)
- if p1 == self and p1 == p2:
- return [self]
- return []
- return other.intersection(self)
- def is_collinear(self, *args):
- """Returns `True` if there exists a line
- that contains `self` and `points`. Returns `False` otherwise.
- A trivially True value is returned if no points are given.
- Parameters
- ==========
- args : sequence of Points
- Returns
- =======
- is_collinear : boolean
- See Also
- ========
- sympy.geometry.line.Line
- Examples
- ========
- >>> from sympy import Point
- >>> from sympy.abc import x
- >>> p1, p2 = Point(0, 0), Point(1, 1)
- >>> p3, p4, p5 = Point(2, 2), Point(x, x), Point(1, 2)
- >>> Point.is_collinear(p1, p2, p3, p4)
- True
- >>> Point.is_collinear(p1, p2, p3, p5)
- False
- """
- points = (self,) + args
- points = Point._normalize_dimension(*[Point(i) for i in points])
- points = list(uniq(points))
- return Point.affine_rank(*points) <= 1
- def is_concyclic(self, *args):
- """Do `self` and the given sequence of points lie in a circle?
- Returns True if the set of points are concyclic and
- False otherwise. A trivial value of True is returned
- if there are fewer than 2 other points.
- Parameters
- ==========
- args : sequence of Points
- Returns
- =======
- is_concyclic : boolean
- Examples
- ========
- >>> from sympy import Point
- Define 4 points that are on the unit circle:
- >>> p1, p2, p3, p4 = Point(1, 0), (0, 1), (-1, 0), (0, -1)
- >>> p1.is_concyclic() == p1.is_concyclic(p2, p3, p4) == True
- True
- Define a point not on that circle:
- >>> p = Point(1, 1)
- >>> p.is_concyclic(p1, p2, p3)
- False
- """
- points = (self,) + args
- points = Point._normalize_dimension(*[Point(i) for i in points])
- points = list(uniq(points))
- if not Point.affine_rank(*points) <= 2:
- return False
- origin = points[0]
- points = [p - origin for p in points]
- # points are concyclic if they are coplanar and
- # there is a point c so that ||p_i-c|| == ||p_j-c|| for all
- # i and j. Rearranging this equation gives us the following
- # condition: the matrix `mat` must not a pivot in the last
- # column.
- mat = Matrix([list(i) + [i.dot(i)] for i in points])
- rref, pivots = mat.rref()
- if len(origin) not in pivots:
- return True
- return False
- @property
- def is_nonzero(self):
- """True if any coordinate is nonzero, False if every coordinate is zero,
- and None if it cannot be determined."""
- is_zero = self.is_zero
- if is_zero is None:
- return None
- return not is_zero
- def is_scalar_multiple(self, p):
- """Returns whether each coordinate of `self` is a scalar
- multiple of the corresponding coordinate in point p.
- """
- s, o = Point._normalize_dimension(self, Point(p))
- # 2d points happen a lot, so optimize this function call
- if s.ambient_dimension == 2:
- (x1, y1), (x2, y2) = s.args, o.args
- rv = (x1*y2 - x2*y1).equals(0)
- if rv is None:
- raise Undecidable(filldedent(
- '''Cannot determine if %s is a scalar multiple of
- %s''' % (s, o)))
- # if the vectors p1 and p2 are linearly dependent, then they must
- # be scalar multiples of each other
- m = Matrix([s.args, o.args])
- return m.rank() < 2
- @property
- def is_zero(self):
- """True if every coordinate is zero, False if any coordinate is not zero,
- and None if it cannot be determined."""
- nonzero = [x.is_nonzero for x in self.args]
- if any(nonzero):
- return False
- if any(x is None for x in nonzero):
- return None
- return True
- @property
- def length(self):
- """
- Treating a Point as a Line, this returns 0 for the length of a Point.
- Examples
- ========
- >>> from sympy import Point
- >>> p = Point(0, 1)
- >>> p.length
- 0
- """
- return S.Zero
- def midpoint(self, p):
- """The midpoint between self and point p.
- Parameters
- ==========
- p : Point
- Returns
- =======
- midpoint : Point
- See Also
- ========
- sympy.geometry.line.Segment.midpoint
- Examples
- ========
- >>> from sympy import Point
- >>> p1, p2 = Point(1, 1), Point(13, 5)
- >>> p1.midpoint(p2)
- Point2D(7, 3)
- """
- s, p = Point._normalize_dimension(self, Point(p))
- return Point([simplify((a + b)*S.Half) for a, b in zip(s, p)])
- @property
- def origin(self):
- """A point of all zeros of the same ambient dimension
- as the current point"""
- return Point([0]*len(self), evaluate=False)
- @property
- def orthogonal_direction(self):
- """Returns a non-zero point that is orthogonal to the
- line containing `self` and the origin.
- Examples
- ========
- >>> from sympy import Line, Point
- >>> a = Point(1, 2, 3)
- >>> a.orthogonal_direction
- Point3D(-2, 1, 0)
- >>> b = _
- >>> Line(b, b.origin).is_perpendicular(Line(a, a.origin))
- True
- """
- dim = self.ambient_dimension
- # if a coordinate is zero, we can put a 1 there and zeros elsewhere
- if self[0].is_zero:
- return Point([1] + (dim - 1)*[0])
- if self[1].is_zero:
- return Point([0,1] + (dim - 2)*[0])
- # if the first two coordinates aren't zero, we can create a non-zero
- # orthogonal vector by swapping them, negating one, and padding with zeros
- return Point([-self[1], self[0]] + (dim - 2)*[0])
- @staticmethod
- def project(a, b):
- """Project the point `a` onto the line between the origin
- and point `b` along the normal direction.
- Parameters
- ==========
- a : Point
- b : Point
- Returns
- =======
- p : Point
- See Also
- ========
- sympy.geometry.line.LinearEntity.projection
- Examples
- ========
- >>> from sympy import Line, Point
- >>> a = Point(1, 2)
- >>> b = Point(2, 5)
- >>> z = a.origin
- >>> p = Point.project(a, b)
- >>> Line(p, a).is_perpendicular(Line(p, b))
- True
- >>> Point.is_collinear(z, p, b)
- True
- """
- a, b = Point._normalize_dimension(Point(a), Point(b))
- if b.is_zero:
- raise ValueError("Cannot project to the zero vector.")
- return b*(a.dot(b) / b.dot(b))
- def taxicab_distance(self, p):
- """The Taxicab Distance from self to point p.
- Returns the sum of the horizontal and vertical distances to point p.
- Parameters
- ==========
- p : Point
- Returns
- =======
- taxicab_distance : The sum of the horizontal
- and vertical distances to point p.
- See Also
- ========
- sympy.geometry.point.Point.distance
- Examples
- ========
- >>> from sympy import Point
- >>> p1, p2 = Point(1, 1), Point(4, 5)
- >>> p1.taxicab_distance(p2)
- 7
- """
- s, p = Point._normalize_dimension(self, Point(p))
- return Add(*(abs(a - b) for a, b in zip(s, p)))
- def canberra_distance(self, p):
- """The Canberra Distance from self to point p.
- Returns the weighted sum of horizontal and vertical distances to
- point p.
- Parameters
- ==========
- p : Point
- Returns
- =======
- canberra_distance : The weighted sum of horizontal and vertical
- distances to point p. The weight used is the sum of absolute values
- of the coordinates.
- Examples
- ========
- >>> from sympy import Point
- >>> p1, p2 = Point(1, 1), Point(3, 3)
- >>> p1.canberra_distance(p2)
- 1
- >>> p1, p2 = Point(0, 0), Point(3, 3)
- >>> p1.canberra_distance(p2)
- 2
- Raises
- ======
- ValueError when both vectors are zero.
- See Also
- ========
- sympy.geometry.point.Point.distance
- """
- s, p = Point._normalize_dimension(self, Point(p))
- if self.is_zero and p.is_zero:
- raise ValueError("Cannot project to the zero vector.")
- return Add(*((abs(a - b)/(abs(a) + abs(b))) for a, b in zip(s, p)))
- @property
- def unit(self):
- """Return the Point that is in the same direction as `self`
- and a distance of 1 from the origin"""
- return self / abs(self)
- class Point2D(Point):
- """A point in a 2-dimensional Euclidean space.
- Parameters
- ==========
- coords : sequence of 2 coordinate values.
- Attributes
- ==========
- x
- y
- length
- Raises
- ======
- TypeError
- When trying to add or subtract points with different dimensions.
- When trying to create a point with more than two dimensions.
- When `intersection` is called with object other than a Point.
- See Also
- ========
- sympy.geometry.line.Segment : Connects two Points
- Examples
- ========
- >>> from sympy import Point2D
- >>> from sympy.abc import x
- >>> Point2D(1, 2)
- Point2D(1, 2)
- >>> Point2D([1, 2])
- Point2D(1, 2)
- >>> Point2D(0, x)
- Point2D(0, x)
- Floats are automatically converted to Rational unless the
- evaluate flag is False:
- >>> Point2D(0.5, 0.25)
- Point2D(1/2, 1/4)
- >>> Point2D(0.5, 0.25, evaluate=False)
- Point2D(0.5, 0.25)
- """
- _ambient_dimension = 2
- def __new__(cls, *args, _nocheck=False, **kwargs):
- if not _nocheck:
- kwargs['dim'] = 2
- args = Point(*args, **kwargs)
- return GeometryEntity.__new__(cls, *args)
- def __contains__(self, item):
- return item == self
- @property
- def bounds(self):
- """Return a tuple (xmin, ymin, xmax, ymax) representing the bounding
- rectangle for the geometric figure.
- """
- return (self.x, self.y, self.x, self.y)
- def rotate(self, angle, pt=None):
- """Rotate ``angle`` radians counterclockwise about Point ``pt``.
- See Also
- ========
- translate, scale
- Examples
- ========
- >>> from sympy import Point2D, pi
- >>> t = Point2D(1, 0)
- >>> t.rotate(pi/2)
- Point2D(0, 1)
- >>> t.rotate(pi/2, (2, 0))
- Point2D(2, -1)
- """
- c = cos(angle)
- s = sin(angle)
- rv = self
- if pt is not None:
- pt = Point(pt, dim=2)
- rv -= pt
- x, y = rv.args
- rv = Point(c*x - s*y, s*x + c*y)
- if pt is not None:
- rv += pt
- return rv
- def scale(self, x=1, y=1, pt=None):
- """Scale the coordinates of the Point by multiplying by
- ``x`` and ``y`` after subtracting ``pt`` -- default is (0, 0) --
- and then adding ``pt`` back again (i.e. ``pt`` is the point of
- reference for the scaling).
- See Also
- ========
- rotate, translate
- Examples
- ========
- >>> from sympy import Point2D
- >>> t = Point2D(1, 1)
- >>> t.scale(2)
- Point2D(2, 1)
- >>> t.scale(2, 2)
- Point2D(2, 2)
- """
- if pt:
- pt = Point(pt, dim=2)
- return self.translate(*(-pt).args).scale(x, y).translate(*pt.args)
- return Point(self.x*x, self.y*y)
- def transform(self, matrix):
- """Return the point after applying the transformation described
- by the 3x3 Matrix, ``matrix``.
- See Also
- ========
- sympy.geometry.point.Point2D.rotate
- sympy.geometry.point.Point2D.scale
- sympy.geometry.point.Point2D.translate
- """
- if not (matrix.is_Matrix and matrix.shape == (3, 3)):
- raise ValueError("matrix must be a 3x3 matrix")
- x, y = self.args
- return Point(*(Matrix(1, 3, [x, y, 1])*matrix).tolist()[0][:2])
- def translate(self, x=0, y=0):
- """Shift the Point by adding x and y to the coordinates of the Point.
- See Also
- ========
- sympy.geometry.point.Point2D.rotate, scale
- Examples
- ========
- >>> from sympy import Point2D
- >>> t = Point2D(0, 1)
- >>> t.translate(2)
- Point2D(2, 1)
- >>> t.translate(2, 2)
- Point2D(2, 3)
- >>> t + Point2D(2, 2)
- Point2D(2, 3)
- """
- return Point(self.x + x, self.y + y)
- @property
- def coordinates(self):
- """
- Returns the two coordinates of the Point.
- Examples
- ========
- >>> from sympy import Point2D
- >>> p = Point2D(0, 1)
- >>> p.coordinates
- (0, 1)
- """
- return self.args
- @property
- def x(self):
- """
- Returns the X coordinate of the Point.
- Examples
- ========
- >>> from sympy import Point2D
- >>> p = Point2D(0, 1)
- >>> p.x
- 0
- """
- return self.args[0]
- @property
- def y(self):
- """
- Returns the Y coordinate of the Point.
- Examples
- ========
- >>> from sympy import Point2D
- >>> p = Point2D(0, 1)
- >>> p.y
- 1
- """
- return self.args[1]
- class Point3D(Point):
- """A point in a 3-dimensional Euclidean space.
- Parameters
- ==========
- coords : sequence of 3 coordinate values.
- Attributes
- ==========
- x
- y
- z
- length
- Raises
- ======
- TypeError
- When trying to add or subtract points with different dimensions.
- When `intersection` is called with object other than a Point.
- Examples
- ========
- >>> from sympy import Point3D
- >>> from sympy.abc import x
- >>> Point3D(1, 2, 3)
- Point3D(1, 2, 3)
- >>> Point3D([1, 2, 3])
- Point3D(1, 2, 3)
- >>> Point3D(0, x, 3)
- Point3D(0, x, 3)
- Floats are automatically converted to Rational unless the
- evaluate flag is False:
- >>> Point3D(0.5, 0.25, 2)
- Point3D(1/2, 1/4, 2)
- >>> Point3D(0.5, 0.25, 3, evaluate=False)
- Point3D(0.5, 0.25, 3)
- """
- _ambient_dimension = 3
- def __new__(cls, *args, _nocheck=False, **kwargs):
- if not _nocheck:
- kwargs['dim'] = 3
- args = Point(*args, **kwargs)
- return GeometryEntity.__new__(cls, *args)
- def __contains__(self, item):
- return item == self
- @staticmethod
- def are_collinear(*points):
- """Is a sequence of points collinear?
- Test whether or not a set of points are collinear. Returns True if
- the set of points are collinear, or False otherwise.
- Parameters
- ==========
- points : sequence of Point
- Returns
- =======
- are_collinear : boolean
- See Also
- ========
- sympy.geometry.line.Line3D
- Examples
- ========
- >>> from sympy import Point3D
- >>> from sympy.abc import x
- >>> p1, p2 = Point3D(0, 0, 0), Point3D(1, 1, 1)
- >>> p3, p4, p5 = Point3D(2, 2, 2), Point3D(x, x, x), Point3D(1, 2, 6)
- >>> Point3D.are_collinear(p1, p2, p3, p4)
- True
- >>> Point3D.are_collinear(p1, p2, p3, p5)
- False
- """
- return Point.is_collinear(*points)
- def direction_cosine(self, point):
- """
- Gives the direction cosine between 2 points
- Parameters
- ==========
- p : Point3D
- Returns
- =======
- list
- Examples
- ========
- >>> from sympy import Point3D
- >>> p1 = Point3D(1, 2, 3)
- >>> p1.direction_cosine(Point3D(2, 3, 5))
- [sqrt(6)/6, sqrt(6)/6, sqrt(6)/3]
- """
- a = self.direction_ratio(point)
- b = sqrt(Add(*(i**2 for i in a)))
- return [(point.x - self.x) / b,(point.y - self.y) / b,
- (point.z - self.z) / b]
- def direction_ratio(self, point):
- """
- Gives the direction ratio between 2 points
- Parameters
- ==========
- p : Point3D
- Returns
- =======
- list
- Examples
- ========
- >>> from sympy import Point3D
- >>> p1 = Point3D(1, 2, 3)
- >>> p1.direction_ratio(Point3D(2, 3, 5))
- [1, 1, 2]
- """
- return [(point.x - self.x),(point.y - self.y),(point.z - self.z)]
- def intersection(self, other):
- """The intersection between this point and another GeometryEntity.
- Parameters
- ==========
- other : GeometryEntity or sequence of coordinates
- Returns
- =======
- intersection : list of Points
- Notes
- =====
- The return value will either be an empty list if there is no
- intersection, otherwise it will contain this point.
- Examples
- ========
- >>> from sympy import Point3D
- >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(1, 1, 1), Point3D(0, 0, 0)
- >>> p1.intersection(p2)
- []
- >>> p1.intersection(p3)
- [Point3D(0, 0, 0)]
- """
- if not isinstance(other, GeometryEntity):
- other = Point(other, dim=3)
- if isinstance(other, Point3D):
- if self == other:
- return [self]
- return []
- return other.intersection(self)
- def scale(self, x=1, y=1, z=1, pt=None):
- """Scale the coordinates of the Point by multiplying by
- ``x`` and ``y`` after subtracting ``pt`` -- default is (0, 0) --
- and then adding ``pt`` back again (i.e. ``pt`` is the point of
- reference for the scaling).
- See Also
- ========
- translate
- Examples
- ========
- >>> from sympy import Point3D
- >>> t = Point3D(1, 1, 1)
- >>> t.scale(2)
- Point3D(2, 1, 1)
- >>> t.scale(2, 2)
- Point3D(2, 2, 1)
- """
- if pt:
- pt = Point3D(pt)
- return self.translate(*(-pt).args).scale(x, y, z).translate(*pt.args)
- return Point3D(self.x*x, self.y*y, self.z*z)
- def transform(self, matrix):
- """Return the point after applying the transformation described
- by the 4x4 Matrix, ``matrix``.
- See Also
- ========
- sympy.geometry.point.Point3D.scale
- sympy.geometry.point.Point3D.translate
- """
- if not (matrix.is_Matrix and matrix.shape == (4, 4)):
- raise ValueError("matrix must be a 4x4 matrix")
- x, y, z = self.args
- m = Transpose(matrix)
- return Point3D(*(Matrix(1, 4, [x, y, z, 1])*m).tolist()[0][:3])
- def translate(self, x=0, y=0, z=0):
- """Shift the Point by adding x and y to the coordinates of the Point.
- See Also
- ========
- scale
- Examples
- ========
- >>> from sympy import Point3D
- >>> t = Point3D(0, 1, 1)
- >>> t.translate(2)
- Point3D(2, 1, 1)
- >>> t.translate(2, 2)
- Point3D(2, 3, 1)
- >>> t + Point3D(2, 2, 2)
- Point3D(2, 3, 3)
- """
- return Point3D(self.x + x, self.y + y, self.z + z)
- @property
- def coordinates(self):
- """
- Returns the three coordinates of the Point.
- Examples
- ========
- >>> from sympy import Point3D
- >>> p = Point3D(0, 1, 2)
- >>> p.coordinates
- (0, 1, 2)
- """
- return self.args
- @property
- def x(self):
- """
- Returns the X coordinate of the Point.
- Examples
- ========
- >>> from sympy import Point3D
- >>> p = Point3D(0, 1, 3)
- >>> p.x
- 0
- """
- return self.args[0]
- @property
- def y(self):
- """
- Returns the Y coordinate of the Point.
- Examples
- ========
- >>> from sympy import Point3D
- >>> p = Point3D(0, 1, 2)
- >>> p.y
- 1
- """
- return self.args[1]
- @property
- def z(self):
- """
- Returns the Z coordinate of the Point.
- Examples
- ========
- >>> from sympy import Point3D
- >>> p = Point3D(0, 1, 1)
- >>> p.z
- 1
- """
- return self.args[2]
|