point.py 36 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376
  1. """Geometrical Points.
  2. Contains
  3. ========
  4. Point
  5. Point2D
  6. Point3D
  7. When methods of Point require 1 or more points as arguments, they
  8. can be passed as a sequence of coordinates or Points:
  9. >>> from sympy import Point
  10. >>> Point(1, 1).is_collinear((2, 2), (3, 4))
  11. False
  12. >>> Point(1, 1).is_collinear(Point(2, 2), Point(3, 4))
  13. False
  14. """
  15. import warnings
  16. from sympy.core import S, sympify, Expr
  17. from sympy.core.add import Add
  18. from sympy.core.containers import Tuple
  19. from sympy.core.numbers import Float
  20. from sympy.core.parameters import global_parameters
  21. from sympy.simplify import nsimplify, simplify
  22. from sympy.geometry.exceptions import GeometryError
  23. from sympy.functions.elementary.miscellaneous import sqrt
  24. from sympy.functions.elementary.complexes import im
  25. from sympy.functions.elementary.trigonometric import cos, sin
  26. from sympy.matrices import Matrix
  27. from sympy.matrices.expressions import Transpose
  28. from sympy.utilities.iterables import uniq, is_sequence
  29. from sympy.utilities.misc import filldedent, func_name, Undecidable
  30. from .entity import GeometryEntity
  31. from mpmath.libmp.libmpf import prec_to_dps
  32. class Point(GeometryEntity):
  33. """A point in a n-dimensional Euclidean space.
  34. Parameters
  35. ==========
  36. coords : sequence of n-coordinate values. In the special
  37. case where n=2 or 3, a Point2D or Point3D will be created
  38. as appropriate.
  39. evaluate : if `True` (default), all floats are turn into
  40. exact types.
  41. dim : number of coordinates the point should have. If coordinates
  42. are unspecified, they are padded with zeros.
  43. on_morph : indicates what should happen when the number of
  44. coordinates of a point need to be changed by adding or
  45. removing zeros. Possible values are `'warn'`, `'error'`, or
  46. `ignore` (default). No warning or error is given when `*args`
  47. is empty and `dim` is given. An error is always raised when
  48. trying to remove nonzero coordinates.
  49. Attributes
  50. ==========
  51. length
  52. origin: A `Point` representing the origin of the
  53. appropriately-dimensioned space.
  54. Raises
  55. ======
  56. TypeError : When instantiating with anything but a Point or sequence
  57. ValueError : when instantiating with a sequence with length < 2 or
  58. when trying to reduce dimensions if keyword `on_morph='error'` is
  59. set.
  60. See Also
  61. ========
  62. sympy.geometry.line.Segment : Connects two Points
  63. Examples
  64. ========
  65. >>> from sympy import Point
  66. >>> from sympy.abc import x
  67. >>> Point(1, 2, 3)
  68. Point3D(1, 2, 3)
  69. >>> Point([1, 2])
  70. Point2D(1, 2)
  71. >>> Point(0, x)
  72. Point2D(0, x)
  73. >>> Point(dim=4)
  74. Point(0, 0, 0, 0)
  75. Floats are automatically converted to Rational unless the
  76. evaluate flag is False:
  77. >>> Point(0.5, 0.25)
  78. Point2D(1/2, 1/4)
  79. >>> Point(0.5, 0.25, evaluate=False)
  80. Point2D(0.5, 0.25)
  81. """
  82. is_Point = True
  83. def __new__(cls, *args, **kwargs):
  84. evaluate = kwargs.get('evaluate', global_parameters.evaluate)
  85. on_morph = kwargs.get('on_morph', 'ignore')
  86. # unpack into coords
  87. coords = args[0] if len(args) == 1 else args
  88. # check args and handle quickly handle Point instances
  89. if isinstance(coords, Point):
  90. # even if we're mutating the dimension of a point, we
  91. # don't reevaluate its coordinates
  92. evaluate = False
  93. if len(coords) == kwargs.get('dim', len(coords)):
  94. return coords
  95. if not is_sequence(coords):
  96. raise TypeError(filldedent('''
  97. Expecting sequence of coordinates, not `{}`'''
  98. .format(func_name(coords))))
  99. # A point where only `dim` is specified is initialized
  100. # to zeros.
  101. if len(coords) == 0 and kwargs.get('dim', None):
  102. coords = (S.Zero,)*kwargs.get('dim')
  103. coords = Tuple(*coords)
  104. dim = kwargs.get('dim', len(coords))
  105. if len(coords) < 2:
  106. raise ValueError(filldedent('''
  107. Point requires 2 or more coordinates or
  108. keyword `dim` > 1.'''))
  109. if len(coords) != dim:
  110. message = ("Dimension of {} needs to be changed "
  111. "from {} to {}.").format(coords, len(coords), dim)
  112. if on_morph == 'ignore':
  113. pass
  114. elif on_morph == "error":
  115. raise ValueError(message)
  116. elif on_morph == 'warn':
  117. warnings.warn(message, stacklevel=2)
  118. else:
  119. raise ValueError(filldedent('''
  120. on_morph value should be 'error',
  121. 'warn' or 'ignore'.'''))
  122. if any(coords[dim:]):
  123. raise ValueError('Nonzero coordinates cannot be removed.')
  124. if any(a.is_number and im(a).is_zero is False for a in coords):
  125. raise ValueError('Imaginary coordinates are not permitted.')
  126. if not all(isinstance(a, Expr) for a in coords):
  127. raise TypeError('Coordinates must be valid SymPy expressions.')
  128. # pad with zeros appropriately
  129. coords = coords[:dim] + (S.Zero,)*(dim - len(coords))
  130. # Turn any Floats into rationals and simplify
  131. # any expressions before we instantiate
  132. if evaluate:
  133. coords = coords.xreplace({
  134. f: simplify(nsimplify(f, rational=True))
  135. for f in coords.atoms(Float)})
  136. # return 2D or 3D instances
  137. if len(coords) == 2:
  138. kwargs['_nocheck'] = True
  139. return Point2D(*coords, **kwargs)
  140. elif len(coords) == 3:
  141. kwargs['_nocheck'] = True
  142. return Point3D(*coords, **kwargs)
  143. # the general Point
  144. return GeometryEntity.__new__(cls, *coords)
  145. def __abs__(self):
  146. """Returns the distance between this point and the origin."""
  147. origin = Point([0]*len(self))
  148. return Point.distance(origin, self)
  149. def __add__(self, other):
  150. """Add other to self by incrementing self's coordinates by
  151. those of other.
  152. Notes
  153. =====
  154. >>> from sympy import Point
  155. When sequences of coordinates are passed to Point methods, they
  156. are converted to a Point internally. This __add__ method does
  157. not do that so if floating point values are used, a floating
  158. point result (in terms of SymPy Floats) will be returned.
  159. >>> Point(1, 2) + (.1, .2)
  160. Point2D(1.1, 2.2)
  161. If this is not desired, the `translate` method can be used or
  162. another Point can be added:
  163. >>> Point(1, 2).translate(.1, .2)
  164. Point2D(11/10, 11/5)
  165. >>> Point(1, 2) + Point(.1, .2)
  166. Point2D(11/10, 11/5)
  167. See Also
  168. ========
  169. sympy.geometry.point.Point.translate
  170. """
  171. try:
  172. s, o = Point._normalize_dimension(self, Point(other, evaluate=False))
  173. except TypeError:
  174. raise GeometryError("Don't know how to add {} and a Point object".format(other))
  175. coords = [simplify(a + b) for a, b in zip(s, o)]
  176. return Point(coords, evaluate=False)
  177. def __contains__(self, item):
  178. return item in self.args
  179. def __truediv__(self, divisor):
  180. """Divide point's coordinates by a factor."""
  181. divisor = sympify(divisor)
  182. coords = [simplify(x/divisor) for x in self.args]
  183. return Point(coords, evaluate=False)
  184. def __eq__(self, other):
  185. if not isinstance(other, Point) or len(self.args) != len(other.args):
  186. return False
  187. return self.args == other.args
  188. def __getitem__(self, key):
  189. return self.args[key]
  190. def __hash__(self):
  191. return hash(self.args)
  192. def __iter__(self):
  193. return self.args.__iter__()
  194. def __len__(self):
  195. return len(self.args)
  196. def __mul__(self, factor):
  197. """Multiply point's coordinates by a factor.
  198. Notes
  199. =====
  200. >>> from sympy import Point
  201. When multiplying a Point by a floating point number,
  202. the coordinates of the Point will be changed to Floats:
  203. >>> Point(1, 2)*0.1
  204. Point2D(0.1, 0.2)
  205. If this is not desired, the `scale` method can be used or
  206. else only multiply or divide by integers:
  207. >>> Point(1, 2).scale(1.1, 1.1)
  208. Point2D(11/10, 11/5)
  209. >>> Point(1, 2)*11/10
  210. Point2D(11/10, 11/5)
  211. See Also
  212. ========
  213. sympy.geometry.point.Point.scale
  214. """
  215. factor = sympify(factor)
  216. coords = [simplify(x*factor) for x in self.args]
  217. return Point(coords, evaluate=False)
  218. def __rmul__(self, factor):
  219. """Multiply a factor by point's coordinates."""
  220. return self.__mul__(factor)
  221. def __neg__(self):
  222. """Negate the point."""
  223. coords = [-x for x in self.args]
  224. return Point(coords, evaluate=False)
  225. def __sub__(self, other):
  226. """Subtract two points, or subtract a factor from this point's
  227. coordinates."""
  228. return self + [-x for x in other]
  229. @classmethod
  230. def _normalize_dimension(cls, *points, **kwargs):
  231. """Ensure that points have the same dimension.
  232. By default `on_morph='warn'` is passed to the
  233. `Point` constructor."""
  234. # if we have a built-in ambient dimension, use it
  235. dim = getattr(cls, '_ambient_dimension', None)
  236. # override if we specified it
  237. dim = kwargs.get('dim', dim)
  238. # if no dim was given, use the highest dimensional point
  239. if dim is None:
  240. dim = max(i.ambient_dimension for i in points)
  241. if all(i.ambient_dimension == dim for i in points):
  242. return list(points)
  243. kwargs['dim'] = dim
  244. kwargs['on_morph'] = kwargs.get('on_morph', 'warn')
  245. return [Point(i, **kwargs) for i in points]
  246. @staticmethod
  247. def affine_rank(*args):
  248. """The affine rank of a set of points is the dimension
  249. of the smallest affine space containing all the points.
  250. For example, if the points lie on a line (and are not all
  251. the same) their affine rank is 1. If the points lie on a plane
  252. but not a line, their affine rank is 2. By convention, the empty
  253. set has affine rank -1."""
  254. if len(args) == 0:
  255. return -1
  256. # make sure we're genuinely points
  257. # and translate every point to the origin
  258. points = Point._normalize_dimension(*[Point(i) for i in args])
  259. origin = points[0]
  260. points = [i - origin for i in points[1:]]
  261. m = Matrix([i.args for i in points])
  262. # XXX fragile -- what is a better way?
  263. return m.rank(iszerofunc = lambda x:
  264. abs(x.n(2)) < 1e-12 if x.is_number else x.is_zero)
  265. @property
  266. def ambient_dimension(self):
  267. """Number of components this point has."""
  268. return getattr(self, '_ambient_dimension', len(self))
  269. @classmethod
  270. def are_coplanar(cls, *points):
  271. """Return True if there exists a plane in which all the points
  272. lie. A trivial True value is returned if `len(points) < 3` or
  273. all Points are 2-dimensional.
  274. Parameters
  275. ==========
  276. A set of points
  277. Raises
  278. ======
  279. ValueError : if less than 3 unique points are given
  280. Returns
  281. =======
  282. boolean
  283. Examples
  284. ========
  285. >>> from sympy import Point3D
  286. >>> p1 = Point3D(1, 2, 2)
  287. >>> p2 = Point3D(2, 7, 2)
  288. >>> p3 = Point3D(0, 0, 2)
  289. >>> p4 = Point3D(1, 1, 2)
  290. >>> Point3D.are_coplanar(p1, p2, p3, p4)
  291. True
  292. >>> p5 = Point3D(0, 1, 3)
  293. >>> Point3D.are_coplanar(p1, p2, p3, p5)
  294. False
  295. """
  296. if len(points) <= 1:
  297. return True
  298. points = cls._normalize_dimension(*[Point(i) for i in points])
  299. # quick exit if we are in 2D
  300. if points[0].ambient_dimension == 2:
  301. return True
  302. points = list(uniq(points))
  303. return Point.affine_rank(*points) <= 2
  304. def distance(self, other):
  305. """The Euclidean distance between self and another GeometricEntity.
  306. Returns
  307. =======
  308. distance : number or symbolic expression.
  309. Raises
  310. ======
  311. TypeError : if other is not recognized as a GeometricEntity or is a
  312. GeometricEntity for which distance is not defined.
  313. See Also
  314. ========
  315. sympy.geometry.line.Segment.length
  316. sympy.geometry.point.Point.taxicab_distance
  317. Examples
  318. ========
  319. >>> from sympy import Point, Line
  320. >>> p1, p2 = Point(1, 1), Point(4, 5)
  321. >>> l = Line((3, 1), (2, 2))
  322. >>> p1.distance(p2)
  323. 5
  324. >>> p1.distance(l)
  325. sqrt(2)
  326. The computed distance may be symbolic, too:
  327. >>> from sympy.abc import x, y
  328. >>> p3 = Point(x, y)
  329. >>> p3.distance((0, 0))
  330. sqrt(x**2 + y**2)
  331. """
  332. if not isinstance(other, GeometryEntity):
  333. try:
  334. other = Point(other, dim=self.ambient_dimension)
  335. except TypeError:
  336. raise TypeError("not recognized as a GeometricEntity: %s" % type(other))
  337. if isinstance(other, Point):
  338. s, p = Point._normalize_dimension(self, Point(other))
  339. return sqrt(Add(*((a - b)**2 for a, b in zip(s, p))))
  340. distance = getattr(other, 'distance', None)
  341. if distance is None:
  342. raise TypeError("distance between Point and %s is not defined" % type(other))
  343. return distance(self)
  344. def dot(self, p):
  345. """Return dot product of self with another Point."""
  346. if not is_sequence(p):
  347. p = Point(p) # raise the error via Point
  348. return Add(*(a*b for a, b in zip(self, p)))
  349. def equals(self, other):
  350. """Returns whether the coordinates of self and other agree."""
  351. # a point is equal to another point if all its components are equal
  352. if not isinstance(other, Point) or len(self) != len(other):
  353. return False
  354. return all(a.equals(b) for a, b in zip(self, other))
  355. def _eval_evalf(self, prec=15, **options):
  356. """Evaluate the coordinates of the point.
  357. This method will, where possible, create and return a new Point
  358. where the coordinates are evaluated as floating point numbers to
  359. the precision indicated (default=15).
  360. Parameters
  361. ==========
  362. prec : int
  363. Returns
  364. =======
  365. point : Point
  366. Examples
  367. ========
  368. >>> from sympy import Point, Rational
  369. >>> p1 = Point(Rational(1, 2), Rational(3, 2))
  370. >>> p1
  371. Point2D(1/2, 3/2)
  372. >>> p1.evalf()
  373. Point2D(0.5, 1.5)
  374. """
  375. dps = prec_to_dps(prec)
  376. coords = [x.evalf(n=dps, **options) for x in self.args]
  377. return Point(*coords, evaluate=False)
  378. def intersection(self, other):
  379. """The intersection between this point and another GeometryEntity.
  380. Parameters
  381. ==========
  382. other : GeometryEntity or sequence of coordinates
  383. Returns
  384. =======
  385. intersection : list of Points
  386. Notes
  387. =====
  388. The return value will either be an empty list if there is no
  389. intersection, otherwise it will contain this point.
  390. Examples
  391. ========
  392. >>> from sympy import Point
  393. >>> p1, p2, p3 = Point(0, 0), Point(1, 1), Point(0, 0)
  394. >>> p1.intersection(p2)
  395. []
  396. >>> p1.intersection(p3)
  397. [Point2D(0, 0)]
  398. """
  399. if not isinstance(other, GeometryEntity):
  400. other = Point(other)
  401. if isinstance(other, Point):
  402. if self == other:
  403. return [self]
  404. p1, p2 = Point._normalize_dimension(self, other)
  405. if p1 == self and p1 == p2:
  406. return [self]
  407. return []
  408. return other.intersection(self)
  409. def is_collinear(self, *args):
  410. """Returns `True` if there exists a line
  411. that contains `self` and `points`. Returns `False` otherwise.
  412. A trivially True value is returned if no points are given.
  413. Parameters
  414. ==========
  415. args : sequence of Points
  416. Returns
  417. =======
  418. is_collinear : boolean
  419. See Also
  420. ========
  421. sympy.geometry.line.Line
  422. Examples
  423. ========
  424. >>> from sympy import Point
  425. >>> from sympy.abc import x
  426. >>> p1, p2 = Point(0, 0), Point(1, 1)
  427. >>> p3, p4, p5 = Point(2, 2), Point(x, x), Point(1, 2)
  428. >>> Point.is_collinear(p1, p2, p3, p4)
  429. True
  430. >>> Point.is_collinear(p1, p2, p3, p5)
  431. False
  432. """
  433. points = (self,) + args
  434. points = Point._normalize_dimension(*[Point(i) for i in points])
  435. points = list(uniq(points))
  436. return Point.affine_rank(*points) <= 1
  437. def is_concyclic(self, *args):
  438. """Do `self` and the given sequence of points lie in a circle?
  439. Returns True if the set of points are concyclic and
  440. False otherwise. A trivial value of True is returned
  441. if there are fewer than 2 other points.
  442. Parameters
  443. ==========
  444. args : sequence of Points
  445. Returns
  446. =======
  447. is_concyclic : boolean
  448. Examples
  449. ========
  450. >>> from sympy import Point
  451. Define 4 points that are on the unit circle:
  452. >>> p1, p2, p3, p4 = Point(1, 0), (0, 1), (-1, 0), (0, -1)
  453. >>> p1.is_concyclic() == p1.is_concyclic(p2, p3, p4) == True
  454. True
  455. Define a point not on that circle:
  456. >>> p = Point(1, 1)
  457. >>> p.is_concyclic(p1, p2, p3)
  458. False
  459. """
  460. points = (self,) + args
  461. points = Point._normalize_dimension(*[Point(i) for i in points])
  462. points = list(uniq(points))
  463. if not Point.affine_rank(*points) <= 2:
  464. return False
  465. origin = points[0]
  466. points = [p - origin for p in points]
  467. # points are concyclic if they are coplanar and
  468. # there is a point c so that ||p_i-c|| == ||p_j-c|| for all
  469. # i and j. Rearranging this equation gives us the following
  470. # condition: the matrix `mat` must not a pivot in the last
  471. # column.
  472. mat = Matrix([list(i) + [i.dot(i)] for i in points])
  473. rref, pivots = mat.rref()
  474. if len(origin) not in pivots:
  475. return True
  476. return False
  477. @property
  478. def is_nonzero(self):
  479. """True if any coordinate is nonzero, False if every coordinate is zero,
  480. and None if it cannot be determined."""
  481. is_zero = self.is_zero
  482. if is_zero is None:
  483. return None
  484. return not is_zero
  485. def is_scalar_multiple(self, p):
  486. """Returns whether each coordinate of `self` is a scalar
  487. multiple of the corresponding coordinate in point p.
  488. """
  489. s, o = Point._normalize_dimension(self, Point(p))
  490. # 2d points happen a lot, so optimize this function call
  491. if s.ambient_dimension == 2:
  492. (x1, y1), (x2, y2) = s.args, o.args
  493. rv = (x1*y2 - x2*y1).equals(0)
  494. if rv is None:
  495. raise Undecidable(filldedent(
  496. '''Cannot determine if %s is a scalar multiple of
  497. %s''' % (s, o)))
  498. # if the vectors p1 and p2 are linearly dependent, then they must
  499. # be scalar multiples of each other
  500. m = Matrix([s.args, o.args])
  501. return m.rank() < 2
  502. @property
  503. def is_zero(self):
  504. """True if every coordinate is zero, False if any coordinate is not zero,
  505. and None if it cannot be determined."""
  506. nonzero = [x.is_nonzero for x in self.args]
  507. if any(nonzero):
  508. return False
  509. if any(x is None for x in nonzero):
  510. return None
  511. return True
  512. @property
  513. def length(self):
  514. """
  515. Treating a Point as a Line, this returns 0 for the length of a Point.
  516. Examples
  517. ========
  518. >>> from sympy import Point
  519. >>> p = Point(0, 1)
  520. >>> p.length
  521. 0
  522. """
  523. return S.Zero
  524. def midpoint(self, p):
  525. """The midpoint between self and point p.
  526. Parameters
  527. ==========
  528. p : Point
  529. Returns
  530. =======
  531. midpoint : Point
  532. See Also
  533. ========
  534. sympy.geometry.line.Segment.midpoint
  535. Examples
  536. ========
  537. >>> from sympy import Point
  538. >>> p1, p2 = Point(1, 1), Point(13, 5)
  539. >>> p1.midpoint(p2)
  540. Point2D(7, 3)
  541. """
  542. s, p = Point._normalize_dimension(self, Point(p))
  543. return Point([simplify((a + b)*S.Half) for a, b in zip(s, p)])
  544. @property
  545. def origin(self):
  546. """A point of all zeros of the same ambient dimension
  547. as the current point"""
  548. return Point([0]*len(self), evaluate=False)
  549. @property
  550. def orthogonal_direction(self):
  551. """Returns a non-zero point that is orthogonal to the
  552. line containing `self` and the origin.
  553. Examples
  554. ========
  555. >>> from sympy import Line, Point
  556. >>> a = Point(1, 2, 3)
  557. >>> a.orthogonal_direction
  558. Point3D(-2, 1, 0)
  559. >>> b = _
  560. >>> Line(b, b.origin).is_perpendicular(Line(a, a.origin))
  561. True
  562. """
  563. dim = self.ambient_dimension
  564. # if a coordinate is zero, we can put a 1 there and zeros elsewhere
  565. if self[0].is_zero:
  566. return Point([1] + (dim - 1)*[0])
  567. if self[1].is_zero:
  568. return Point([0,1] + (dim - 2)*[0])
  569. # if the first two coordinates aren't zero, we can create a non-zero
  570. # orthogonal vector by swapping them, negating one, and padding with zeros
  571. return Point([-self[1], self[0]] + (dim - 2)*[0])
  572. @staticmethod
  573. def project(a, b):
  574. """Project the point `a` onto the line between the origin
  575. and point `b` along the normal direction.
  576. Parameters
  577. ==========
  578. a : Point
  579. b : Point
  580. Returns
  581. =======
  582. p : Point
  583. See Also
  584. ========
  585. sympy.geometry.line.LinearEntity.projection
  586. Examples
  587. ========
  588. >>> from sympy import Line, Point
  589. >>> a = Point(1, 2)
  590. >>> b = Point(2, 5)
  591. >>> z = a.origin
  592. >>> p = Point.project(a, b)
  593. >>> Line(p, a).is_perpendicular(Line(p, b))
  594. True
  595. >>> Point.is_collinear(z, p, b)
  596. True
  597. """
  598. a, b = Point._normalize_dimension(Point(a), Point(b))
  599. if b.is_zero:
  600. raise ValueError("Cannot project to the zero vector.")
  601. return b*(a.dot(b) / b.dot(b))
  602. def taxicab_distance(self, p):
  603. """The Taxicab Distance from self to point p.
  604. Returns the sum of the horizontal and vertical distances to point p.
  605. Parameters
  606. ==========
  607. p : Point
  608. Returns
  609. =======
  610. taxicab_distance : The sum of the horizontal
  611. and vertical distances to point p.
  612. See Also
  613. ========
  614. sympy.geometry.point.Point.distance
  615. Examples
  616. ========
  617. >>> from sympy import Point
  618. >>> p1, p2 = Point(1, 1), Point(4, 5)
  619. >>> p1.taxicab_distance(p2)
  620. 7
  621. """
  622. s, p = Point._normalize_dimension(self, Point(p))
  623. return Add(*(abs(a - b) for a, b in zip(s, p)))
  624. def canberra_distance(self, p):
  625. """The Canberra Distance from self to point p.
  626. Returns the weighted sum of horizontal and vertical distances to
  627. point p.
  628. Parameters
  629. ==========
  630. p : Point
  631. Returns
  632. =======
  633. canberra_distance : The weighted sum of horizontal and vertical
  634. distances to point p. The weight used is the sum of absolute values
  635. of the coordinates.
  636. Examples
  637. ========
  638. >>> from sympy import Point
  639. >>> p1, p2 = Point(1, 1), Point(3, 3)
  640. >>> p1.canberra_distance(p2)
  641. 1
  642. >>> p1, p2 = Point(0, 0), Point(3, 3)
  643. >>> p1.canberra_distance(p2)
  644. 2
  645. Raises
  646. ======
  647. ValueError when both vectors are zero.
  648. See Also
  649. ========
  650. sympy.geometry.point.Point.distance
  651. """
  652. s, p = Point._normalize_dimension(self, Point(p))
  653. if self.is_zero and p.is_zero:
  654. raise ValueError("Cannot project to the zero vector.")
  655. return Add(*((abs(a - b)/(abs(a) + abs(b))) for a, b in zip(s, p)))
  656. @property
  657. def unit(self):
  658. """Return the Point that is in the same direction as `self`
  659. and a distance of 1 from the origin"""
  660. return self / abs(self)
  661. class Point2D(Point):
  662. """A point in a 2-dimensional Euclidean space.
  663. Parameters
  664. ==========
  665. coords : sequence of 2 coordinate values.
  666. Attributes
  667. ==========
  668. x
  669. y
  670. length
  671. Raises
  672. ======
  673. TypeError
  674. When trying to add or subtract points with different dimensions.
  675. When trying to create a point with more than two dimensions.
  676. When `intersection` is called with object other than a Point.
  677. See Also
  678. ========
  679. sympy.geometry.line.Segment : Connects two Points
  680. Examples
  681. ========
  682. >>> from sympy import Point2D
  683. >>> from sympy.abc import x
  684. >>> Point2D(1, 2)
  685. Point2D(1, 2)
  686. >>> Point2D([1, 2])
  687. Point2D(1, 2)
  688. >>> Point2D(0, x)
  689. Point2D(0, x)
  690. Floats are automatically converted to Rational unless the
  691. evaluate flag is False:
  692. >>> Point2D(0.5, 0.25)
  693. Point2D(1/2, 1/4)
  694. >>> Point2D(0.5, 0.25, evaluate=False)
  695. Point2D(0.5, 0.25)
  696. """
  697. _ambient_dimension = 2
  698. def __new__(cls, *args, _nocheck=False, **kwargs):
  699. if not _nocheck:
  700. kwargs['dim'] = 2
  701. args = Point(*args, **kwargs)
  702. return GeometryEntity.__new__(cls, *args)
  703. def __contains__(self, item):
  704. return item == self
  705. @property
  706. def bounds(self):
  707. """Return a tuple (xmin, ymin, xmax, ymax) representing the bounding
  708. rectangle for the geometric figure.
  709. """
  710. return (self.x, self.y, self.x, self.y)
  711. def rotate(self, angle, pt=None):
  712. """Rotate ``angle`` radians counterclockwise about Point ``pt``.
  713. See Also
  714. ========
  715. translate, scale
  716. Examples
  717. ========
  718. >>> from sympy import Point2D, pi
  719. >>> t = Point2D(1, 0)
  720. >>> t.rotate(pi/2)
  721. Point2D(0, 1)
  722. >>> t.rotate(pi/2, (2, 0))
  723. Point2D(2, -1)
  724. """
  725. c = cos(angle)
  726. s = sin(angle)
  727. rv = self
  728. if pt is not None:
  729. pt = Point(pt, dim=2)
  730. rv -= pt
  731. x, y = rv.args
  732. rv = Point(c*x - s*y, s*x + c*y)
  733. if pt is not None:
  734. rv += pt
  735. return rv
  736. def scale(self, x=1, y=1, pt=None):
  737. """Scale the coordinates of the Point by multiplying by
  738. ``x`` and ``y`` after subtracting ``pt`` -- default is (0, 0) --
  739. and then adding ``pt`` back again (i.e. ``pt`` is the point of
  740. reference for the scaling).
  741. See Also
  742. ========
  743. rotate, translate
  744. Examples
  745. ========
  746. >>> from sympy import Point2D
  747. >>> t = Point2D(1, 1)
  748. >>> t.scale(2)
  749. Point2D(2, 1)
  750. >>> t.scale(2, 2)
  751. Point2D(2, 2)
  752. """
  753. if pt:
  754. pt = Point(pt, dim=2)
  755. return self.translate(*(-pt).args).scale(x, y).translate(*pt.args)
  756. return Point(self.x*x, self.y*y)
  757. def transform(self, matrix):
  758. """Return the point after applying the transformation described
  759. by the 3x3 Matrix, ``matrix``.
  760. See Also
  761. ========
  762. sympy.geometry.point.Point2D.rotate
  763. sympy.geometry.point.Point2D.scale
  764. sympy.geometry.point.Point2D.translate
  765. """
  766. if not (matrix.is_Matrix and matrix.shape == (3, 3)):
  767. raise ValueError("matrix must be a 3x3 matrix")
  768. x, y = self.args
  769. return Point(*(Matrix(1, 3, [x, y, 1])*matrix).tolist()[0][:2])
  770. def translate(self, x=0, y=0):
  771. """Shift the Point by adding x and y to the coordinates of the Point.
  772. See Also
  773. ========
  774. sympy.geometry.point.Point2D.rotate, scale
  775. Examples
  776. ========
  777. >>> from sympy import Point2D
  778. >>> t = Point2D(0, 1)
  779. >>> t.translate(2)
  780. Point2D(2, 1)
  781. >>> t.translate(2, 2)
  782. Point2D(2, 3)
  783. >>> t + Point2D(2, 2)
  784. Point2D(2, 3)
  785. """
  786. return Point(self.x + x, self.y + y)
  787. @property
  788. def coordinates(self):
  789. """
  790. Returns the two coordinates of the Point.
  791. Examples
  792. ========
  793. >>> from sympy import Point2D
  794. >>> p = Point2D(0, 1)
  795. >>> p.coordinates
  796. (0, 1)
  797. """
  798. return self.args
  799. @property
  800. def x(self):
  801. """
  802. Returns the X coordinate of the Point.
  803. Examples
  804. ========
  805. >>> from sympy import Point2D
  806. >>> p = Point2D(0, 1)
  807. >>> p.x
  808. 0
  809. """
  810. return self.args[0]
  811. @property
  812. def y(self):
  813. """
  814. Returns the Y coordinate of the Point.
  815. Examples
  816. ========
  817. >>> from sympy import Point2D
  818. >>> p = Point2D(0, 1)
  819. >>> p.y
  820. 1
  821. """
  822. return self.args[1]
  823. class Point3D(Point):
  824. """A point in a 3-dimensional Euclidean space.
  825. Parameters
  826. ==========
  827. coords : sequence of 3 coordinate values.
  828. Attributes
  829. ==========
  830. x
  831. y
  832. z
  833. length
  834. Raises
  835. ======
  836. TypeError
  837. When trying to add or subtract points with different dimensions.
  838. When `intersection` is called with object other than a Point.
  839. Examples
  840. ========
  841. >>> from sympy import Point3D
  842. >>> from sympy.abc import x
  843. >>> Point3D(1, 2, 3)
  844. Point3D(1, 2, 3)
  845. >>> Point3D([1, 2, 3])
  846. Point3D(1, 2, 3)
  847. >>> Point3D(0, x, 3)
  848. Point3D(0, x, 3)
  849. Floats are automatically converted to Rational unless the
  850. evaluate flag is False:
  851. >>> Point3D(0.5, 0.25, 2)
  852. Point3D(1/2, 1/4, 2)
  853. >>> Point3D(0.5, 0.25, 3, evaluate=False)
  854. Point3D(0.5, 0.25, 3)
  855. """
  856. _ambient_dimension = 3
  857. def __new__(cls, *args, _nocheck=False, **kwargs):
  858. if not _nocheck:
  859. kwargs['dim'] = 3
  860. args = Point(*args, **kwargs)
  861. return GeometryEntity.__new__(cls, *args)
  862. def __contains__(self, item):
  863. return item == self
  864. @staticmethod
  865. def are_collinear(*points):
  866. """Is a sequence of points collinear?
  867. Test whether or not a set of points are collinear. Returns True if
  868. the set of points are collinear, or False otherwise.
  869. Parameters
  870. ==========
  871. points : sequence of Point
  872. Returns
  873. =======
  874. are_collinear : boolean
  875. See Also
  876. ========
  877. sympy.geometry.line.Line3D
  878. Examples
  879. ========
  880. >>> from sympy import Point3D
  881. >>> from sympy.abc import x
  882. >>> p1, p2 = Point3D(0, 0, 0), Point3D(1, 1, 1)
  883. >>> p3, p4, p5 = Point3D(2, 2, 2), Point3D(x, x, x), Point3D(1, 2, 6)
  884. >>> Point3D.are_collinear(p1, p2, p3, p4)
  885. True
  886. >>> Point3D.are_collinear(p1, p2, p3, p5)
  887. False
  888. """
  889. return Point.is_collinear(*points)
  890. def direction_cosine(self, point):
  891. """
  892. Gives the direction cosine between 2 points
  893. Parameters
  894. ==========
  895. p : Point3D
  896. Returns
  897. =======
  898. list
  899. Examples
  900. ========
  901. >>> from sympy import Point3D
  902. >>> p1 = Point3D(1, 2, 3)
  903. >>> p1.direction_cosine(Point3D(2, 3, 5))
  904. [sqrt(6)/6, sqrt(6)/6, sqrt(6)/3]
  905. """
  906. a = self.direction_ratio(point)
  907. b = sqrt(Add(*(i**2 for i in a)))
  908. return [(point.x - self.x) / b,(point.y - self.y) / b,
  909. (point.z - self.z) / b]
  910. def direction_ratio(self, point):
  911. """
  912. Gives the direction ratio between 2 points
  913. Parameters
  914. ==========
  915. p : Point3D
  916. Returns
  917. =======
  918. list
  919. Examples
  920. ========
  921. >>> from sympy import Point3D
  922. >>> p1 = Point3D(1, 2, 3)
  923. >>> p1.direction_ratio(Point3D(2, 3, 5))
  924. [1, 1, 2]
  925. """
  926. return [(point.x - self.x),(point.y - self.y),(point.z - self.z)]
  927. def intersection(self, other):
  928. """The intersection between this point and another GeometryEntity.
  929. Parameters
  930. ==========
  931. other : GeometryEntity or sequence of coordinates
  932. Returns
  933. =======
  934. intersection : list of Points
  935. Notes
  936. =====
  937. The return value will either be an empty list if there is no
  938. intersection, otherwise it will contain this point.
  939. Examples
  940. ========
  941. >>> from sympy import Point3D
  942. >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(1, 1, 1), Point3D(0, 0, 0)
  943. >>> p1.intersection(p2)
  944. []
  945. >>> p1.intersection(p3)
  946. [Point3D(0, 0, 0)]
  947. """
  948. if not isinstance(other, GeometryEntity):
  949. other = Point(other, dim=3)
  950. if isinstance(other, Point3D):
  951. if self == other:
  952. return [self]
  953. return []
  954. return other.intersection(self)
  955. def scale(self, x=1, y=1, z=1, pt=None):
  956. """Scale the coordinates of the Point by multiplying by
  957. ``x`` and ``y`` after subtracting ``pt`` -- default is (0, 0) --
  958. and then adding ``pt`` back again (i.e. ``pt`` is the point of
  959. reference for the scaling).
  960. See Also
  961. ========
  962. translate
  963. Examples
  964. ========
  965. >>> from sympy import Point3D
  966. >>> t = Point3D(1, 1, 1)
  967. >>> t.scale(2)
  968. Point3D(2, 1, 1)
  969. >>> t.scale(2, 2)
  970. Point3D(2, 2, 1)
  971. """
  972. if pt:
  973. pt = Point3D(pt)
  974. return self.translate(*(-pt).args).scale(x, y, z).translate(*pt.args)
  975. return Point3D(self.x*x, self.y*y, self.z*z)
  976. def transform(self, matrix):
  977. """Return the point after applying the transformation described
  978. by the 4x4 Matrix, ``matrix``.
  979. See Also
  980. ========
  981. sympy.geometry.point.Point3D.scale
  982. sympy.geometry.point.Point3D.translate
  983. """
  984. if not (matrix.is_Matrix and matrix.shape == (4, 4)):
  985. raise ValueError("matrix must be a 4x4 matrix")
  986. x, y, z = self.args
  987. m = Transpose(matrix)
  988. return Point3D(*(Matrix(1, 4, [x, y, z, 1])*m).tolist()[0][:3])
  989. def translate(self, x=0, y=0, z=0):
  990. """Shift the Point by adding x and y to the coordinates of the Point.
  991. See Also
  992. ========
  993. scale
  994. Examples
  995. ========
  996. >>> from sympy import Point3D
  997. >>> t = Point3D(0, 1, 1)
  998. >>> t.translate(2)
  999. Point3D(2, 1, 1)
  1000. >>> t.translate(2, 2)
  1001. Point3D(2, 3, 1)
  1002. >>> t + Point3D(2, 2, 2)
  1003. Point3D(2, 3, 3)
  1004. """
  1005. return Point3D(self.x + x, self.y + y, self.z + z)
  1006. @property
  1007. def coordinates(self):
  1008. """
  1009. Returns the three coordinates of the Point.
  1010. Examples
  1011. ========
  1012. >>> from sympy import Point3D
  1013. >>> p = Point3D(0, 1, 2)
  1014. >>> p.coordinates
  1015. (0, 1, 2)
  1016. """
  1017. return self.args
  1018. @property
  1019. def x(self):
  1020. """
  1021. Returns the X coordinate of the Point.
  1022. Examples
  1023. ========
  1024. >>> from sympy import Point3D
  1025. >>> p = Point3D(0, 1, 3)
  1026. >>> p.x
  1027. 0
  1028. """
  1029. return self.args[0]
  1030. @property
  1031. def y(self):
  1032. """
  1033. Returns the Y coordinate of the Point.
  1034. Examples
  1035. ========
  1036. >>> from sympy import Point3D
  1037. >>> p = Point3D(0, 1, 2)
  1038. >>> p.y
  1039. 1
  1040. """
  1041. return self.args[1]
  1042. @property
  1043. def z(self):
  1044. """
  1045. Returns the Z coordinate of the Point.
  1046. Examples
  1047. ========
  1048. >>> from sympy import Point3D
  1049. >>> p = Point3D(0, 1, 1)
  1050. >>> p.z
  1051. 1
  1052. """
  1053. return self.args[2]