line.py 75 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785
  1. """Line-like geometrical entities.
  2. Contains
  3. ========
  4. LinearEntity
  5. Line
  6. Ray
  7. Segment
  8. LinearEntity2D
  9. Line2D
  10. Ray2D
  11. Segment2D
  12. LinearEntity3D
  13. Line3D
  14. Ray3D
  15. Segment3D
  16. """
  17. from sympy.core.containers import Tuple
  18. from sympy.core.evalf import N
  19. from sympy.core.expr import Expr
  20. from sympy.core.numbers import Rational, oo
  21. from sympy.core.relational import Eq
  22. from sympy.core.singleton import S
  23. from sympy.core.sorting import ordered
  24. from sympy.core.symbol import _symbol, Dummy, uniquely_named_symbol
  25. from sympy.core.sympify import sympify
  26. from sympy.functions.elementary.piecewise import Piecewise
  27. from sympy.functions.elementary.trigonometric import (_pi_coeff as pi_coeff, acos, tan, atan2)
  28. from .entity import GeometryEntity, GeometrySet
  29. from .exceptions import GeometryError
  30. from .point import Point, Point3D
  31. from .util import find, intersection
  32. from sympy.logic.boolalg import And
  33. from sympy.matrices import Matrix
  34. from sympy.sets.sets import Intersection
  35. from sympy.simplify.simplify import simplify
  36. from sympy.solvers.solveset import linear_coeffs
  37. from sympy.utilities.exceptions import sympy_deprecation_warning
  38. from sympy.utilities.misc import Undecidable, filldedent
  39. import random
  40. class LinearEntity(GeometrySet):
  41. """A base class for all linear entities (Line, Ray and Segment)
  42. in n-dimensional Euclidean space.
  43. Attributes
  44. ==========
  45. ambient_dimension
  46. direction
  47. length
  48. p1
  49. p2
  50. points
  51. Notes
  52. =====
  53. This is an abstract class and is not meant to be instantiated.
  54. See Also
  55. ========
  56. sympy.geometry.entity.GeometryEntity
  57. """
  58. def __new__(cls, p1, p2=None, **kwargs):
  59. p1, p2 = Point._normalize_dimension(p1, p2)
  60. if p1 == p2:
  61. # sometimes we return a single point if we are not given two unique
  62. # points. This is done in the specific subclass
  63. raise ValueError(
  64. "%s.__new__ requires two unique Points." % cls.__name__)
  65. if len(p1) != len(p2):
  66. raise ValueError(
  67. "%s.__new__ requires two Points of equal dimension." % cls.__name__)
  68. return GeometryEntity.__new__(cls, p1, p2, **kwargs)
  69. def __contains__(self, other):
  70. """Return a definitive answer or else raise an error if it cannot
  71. be determined that other is on the boundaries of self."""
  72. result = self.contains(other)
  73. if result is not None:
  74. return result
  75. else:
  76. raise Undecidable(
  77. "Cannot decide whether '%s' contains '%s'" % (self, other))
  78. def _span_test(self, other):
  79. """Test whether the point `other` lies in the positive span of `self`.
  80. A point x is 'in front' of a point y if x.dot(y) >= 0. Return
  81. -1 if `other` is behind `self.p1`, 0 if `other` is `self.p1` and
  82. and 1 if `other` is in front of `self.p1`."""
  83. if self.p1 == other:
  84. return 0
  85. rel_pos = other - self.p1
  86. d = self.direction
  87. if d.dot(rel_pos) > 0:
  88. return 1
  89. return -1
  90. @property
  91. def ambient_dimension(self):
  92. """A property method that returns the dimension of LinearEntity
  93. object.
  94. Parameters
  95. ==========
  96. p1 : LinearEntity
  97. Returns
  98. =======
  99. dimension : integer
  100. Examples
  101. ========
  102. >>> from sympy import Point, Line
  103. >>> p1, p2 = Point(0, 0), Point(1, 1)
  104. >>> l1 = Line(p1, p2)
  105. >>> l1.ambient_dimension
  106. 2
  107. >>> from sympy import Point, Line
  108. >>> p1, p2 = Point(0, 0, 0), Point(1, 1, 1)
  109. >>> l1 = Line(p1, p2)
  110. >>> l1.ambient_dimension
  111. 3
  112. """
  113. return len(self.p1)
  114. def angle_between(l1, l2):
  115. """Return the non-reflex angle formed by rays emanating from
  116. the origin with directions the same as the direction vectors
  117. of the linear entities.
  118. Parameters
  119. ==========
  120. l1 : LinearEntity
  121. l2 : LinearEntity
  122. Returns
  123. =======
  124. angle : angle in radians
  125. Notes
  126. =====
  127. From the dot product of vectors v1 and v2 it is known that:
  128. ``dot(v1, v2) = |v1|*|v2|*cos(A)``
  129. where A is the angle formed between the two vectors. We can
  130. get the directional vectors of the two lines and readily
  131. find the angle between the two using the above formula.
  132. See Also
  133. ========
  134. is_perpendicular, Ray2D.closing_angle
  135. Examples
  136. ========
  137. >>> from sympy import Line
  138. >>> e = Line((0, 0), (1, 0))
  139. >>> ne = Line((0, 0), (1, 1))
  140. >>> sw = Line((1, 1), (0, 0))
  141. >>> ne.angle_between(e)
  142. pi/4
  143. >>> sw.angle_between(e)
  144. 3*pi/4
  145. To obtain the non-obtuse angle at the intersection of lines, use
  146. the ``smallest_angle_between`` method:
  147. >>> sw.smallest_angle_between(e)
  148. pi/4
  149. >>> from sympy import Point3D, Line3D
  150. >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(1, 1, 1), Point3D(-1, 2, 0)
  151. >>> l1, l2 = Line3D(p1, p2), Line3D(p2, p3)
  152. >>> l1.angle_between(l2)
  153. acos(-sqrt(2)/3)
  154. >>> l1.smallest_angle_between(l2)
  155. acos(sqrt(2)/3)
  156. """
  157. if not isinstance(l1, LinearEntity) and not isinstance(l2, LinearEntity):
  158. raise TypeError('Must pass only LinearEntity objects')
  159. v1, v2 = l1.direction, l2.direction
  160. return acos(v1.dot(v2)/(abs(v1)*abs(v2)))
  161. def smallest_angle_between(l1, l2):
  162. """Return the smallest angle formed at the intersection of the
  163. lines containing the linear entities.
  164. Parameters
  165. ==========
  166. l1 : LinearEntity
  167. l2 : LinearEntity
  168. Returns
  169. =======
  170. angle : angle in radians
  171. See Also
  172. ========
  173. angle_between, is_perpendicular, Ray2D.closing_angle
  174. Examples
  175. ========
  176. >>> from sympy import Point, Line
  177. >>> p1, p2, p3 = Point(0, 0), Point(0, 4), Point(2, -2)
  178. >>> l1, l2 = Line(p1, p2), Line(p1, p3)
  179. >>> l1.smallest_angle_between(l2)
  180. pi/4
  181. See Also
  182. ========
  183. angle_between, Ray2D.closing_angle
  184. """
  185. if not isinstance(l1, LinearEntity) and not isinstance(l2, LinearEntity):
  186. raise TypeError('Must pass only LinearEntity objects')
  187. v1, v2 = l1.direction, l2.direction
  188. return acos(abs(v1.dot(v2))/(abs(v1)*abs(v2)))
  189. def arbitrary_point(self, parameter='t'):
  190. """A parameterized point on the Line.
  191. Parameters
  192. ==========
  193. parameter : str, optional
  194. The name of the parameter which will be used for the parametric
  195. point. The default value is 't'. When this parameter is 0, the
  196. first point used to define the line will be returned, and when
  197. it is 1 the second point will be returned.
  198. Returns
  199. =======
  200. point : Point
  201. Raises
  202. ======
  203. ValueError
  204. When ``parameter`` already appears in the Line's definition.
  205. See Also
  206. ========
  207. sympy.geometry.point.Point
  208. Examples
  209. ========
  210. >>> from sympy import Point, Line
  211. >>> p1, p2 = Point(1, 0), Point(5, 3)
  212. >>> l1 = Line(p1, p2)
  213. >>> l1.arbitrary_point()
  214. Point2D(4*t + 1, 3*t)
  215. >>> from sympy import Point3D, Line3D
  216. >>> p1, p2 = Point3D(1, 0, 0), Point3D(5, 3, 1)
  217. >>> l1 = Line3D(p1, p2)
  218. >>> l1.arbitrary_point()
  219. Point3D(4*t + 1, 3*t, t)
  220. """
  221. t = _symbol(parameter, real=True)
  222. if t.name in (f.name for f in self.free_symbols):
  223. raise ValueError(filldedent('''
  224. Symbol %s already appears in object
  225. and cannot be used as a parameter.
  226. ''' % t.name))
  227. # multiply on the right so the variable gets
  228. # combined with the coordinates of the point
  229. return self.p1 + (self.p2 - self.p1)*t
  230. @staticmethod
  231. def are_concurrent(*lines):
  232. """Is a sequence of linear entities concurrent?
  233. Two or more linear entities are concurrent if they all
  234. intersect at a single point.
  235. Parameters
  236. ==========
  237. lines : a sequence of linear entities.
  238. Returns
  239. =======
  240. True : if the set of linear entities intersect in one point
  241. False : otherwise.
  242. See Also
  243. ========
  244. sympy.geometry.util.intersection
  245. Examples
  246. ========
  247. >>> from sympy import Point, Line
  248. >>> p1, p2 = Point(0, 0), Point(3, 5)
  249. >>> p3, p4 = Point(-2, -2), Point(0, 2)
  250. >>> l1, l2, l3 = Line(p1, p2), Line(p1, p3), Line(p1, p4)
  251. >>> Line.are_concurrent(l1, l2, l3)
  252. True
  253. >>> l4 = Line(p2, p3)
  254. >>> Line.are_concurrent(l2, l3, l4)
  255. False
  256. >>> from sympy import Point3D, Line3D
  257. >>> p1, p2 = Point3D(0, 0, 0), Point3D(3, 5, 2)
  258. >>> p3, p4 = Point3D(-2, -2, -2), Point3D(0, 2, 1)
  259. >>> l1, l2, l3 = Line3D(p1, p2), Line3D(p1, p3), Line3D(p1, p4)
  260. >>> Line3D.are_concurrent(l1, l2, l3)
  261. True
  262. >>> l4 = Line3D(p2, p3)
  263. >>> Line3D.are_concurrent(l2, l3, l4)
  264. False
  265. """
  266. common_points = Intersection(*lines)
  267. if common_points.is_FiniteSet and len(common_points) == 1:
  268. return True
  269. return False
  270. def contains(self, other):
  271. """Subclasses should implement this method and should return
  272. True if other is on the boundaries of self;
  273. False if not on the boundaries of self;
  274. None if a determination cannot be made."""
  275. raise NotImplementedError()
  276. @property
  277. def direction(self):
  278. """The direction vector of the LinearEntity.
  279. Returns
  280. =======
  281. p : a Point; the ray from the origin to this point is the
  282. direction of `self`
  283. Examples
  284. ========
  285. >>> from sympy import Line
  286. >>> a, b = (1, 1), (1, 3)
  287. >>> Line(a, b).direction
  288. Point2D(0, 2)
  289. >>> Line(b, a).direction
  290. Point2D(0, -2)
  291. This can be reported so the distance from the origin is 1:
  292. >>> Line(b, a).direction.unit
  293. Point2D(0, -1)
  294. See Also
  295. ========
  296. sympy.geometry.point.Point.unit
  297. """
  298. return self.p2 - self.p1
  299. def intersection(self, other):
  300. """The intersection with another geometrical entity.
  301. Parameters
  302. ==========
  303. o : Point or LinearEntity
  304. Returns
  305. =======
  306. intersection : list of geometrical entities
  307. See Also
  308. ========
  309. sympy.geometry.point.Point
  310. Examples
  311. ========
  312. >>> from sympy import Point, Line, Segment
  313. >>> p1, p2, p3 = Point(0, 0), Point(1, 1), Point(7, 7)
  314. >>> l1 = Line(p1, p2)
  315. >>> l1.intersection(p3)
  316. [Point2D(7, 7)]
  317. >>> p4, p5 = Point(5, 0), Point(0, 3)
  318. >>> l2 = Line(p4, p5)
  319. >>> l1.intersection(l2)
  320. [Point2D(15/8, 15/8)]
  321. >>> p6, p7 = Point(0, 5), Point(2, 6)
  322. >>> s1 = Segment(p6, p7)
  323. >>> l1.intersection(s1)
  324. []
  325. >>> from sympy import Point3D, Line3D, Segment3D
  326. >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(1, 1, 1), Point3D(7, 7, 7)
  327. >>> l1 = Line3D(p1, p2)
  328. >>> l1.intersection(p3)
  329. [Point3D(7, 7, 7)]
  330. >>> l1 = Line3D(Point3D(4,19,12), Point3D(5,25,17))
  331. >>> l2 = Line3D(Point3D(-3, -15, -19), direction_ratio=[2,8,8])
  332. >>> l1.intersection(l2)
  333. [Point3D(1, 1, -3)]
  334. >>> p6, p7 = Point3D(0, 5, 2), Point3D(2, 6, 3)
  335. >>> s1 = Segment3D(p6, p7)
  336. >>> l1.intersection(s1)
  337. []
  338. """
  339. def intersect_parallel_rays(ray1, ray2):
  340. if ray1.direction.dot(ray2.direction) > 0:
  341. # rays point in the same direction
  342. # so return the one that is "in front"
  343. return [ray2] if ray1._span_test(ray2.p1) >= 0 else [ray1]
  344. else:
  345. # rays point in opposite directions
  346. st = ray1._span_test(ray2.p1)
  347. if st < 0:
  348. return []
  349. elif st == 0:
  350. return [ray2.p1]
  351. return [Segment(ray1.p1, ray2.p1)]
  352. def intersect_parallel_ray_and_segment(ray, seg):
  353. st1, st2 = ray._span_test(seg.p1), ray._span_test(seg.p2)
  354. if st1 < 0 and st2 < 0:
  355. return []
  356. elif st1 >= 0 and st2 >= 0:
  357. return [seg]
  358. elif st1 >= 0: # st2 < 0:
  359. return [Segment(ray.p1, seg.p1)]
  360. else: # st1 < 0 and st2 >= 0:
  361. return [Segment(ray.p1, seg.p2)]
  362. def intersect_parallel_segments(seg1, seg2):
  363. if seg1.contains(seg2):
  364. return [seg2]
  365. if seg2.contains(seg1):
  366. return [seg1]
  367. # direct the segments so they're oriented the same way
  368. if seg1.direction.dot(seg2.direction) < 0:
  369. seg2 = Segment(seg2.p2, seg2.p1)
  370. # order the segments so seg1 is "behind" seg2
  371. if seg1._span_test(seg2.p1) < 0:
  372. seg1, seg2 = seg2, seg1
  373. if seg2._span_test(seg1.p2) < 0:
  374. return []
  375. return [Segment(seg2.p1, seg1.p2)]
  376. if not isinstance(other, GeometryEntity):
  377. other = Point(other, dim=self.ambient_dimension)
  378. if other.is_Point:
  379. if self.contains(other):
  380. return [other]
  381. else:
  382. return []
  383. elif isinstance(other, LinearEntity):
  384. # break into cases based on whether
  385. # the lines are parallel, non-parallel intersecting, or skew
  386. pts = Point._normalize_dimension(self.p1, self.p2, other.p1, other.p2)
  387. rank = Point.affine_rank(*pts)
  388. if rank == 1:
  389. # we're collinear
  390. if isinstance(self, Line):
  391. return [other]
  392. if isinstance(other, Line):
  393. return [self]
  394. if isinstance(self, Ray) and isinstance(other, Ray):
  395. return intersect_parallel_rays(self, other)
  396. if isinstance(self, Ray) and isinstance(other, Segment):
  397. return intersect_parallel_ray_and_segment(self, other)
  398. if isinstance(self, Segment) and isinstance(other, Ray):
  399. return intersect_parallel_ray_and_segment(other, self)
  400. if isinstance(self, Segment) and isinstance(other, Segment):
  401. return intersect_parallel_segments(self, other)
  402. elif rank == 2:
  403. # we're in the same plane
  404. l1 = Line(*pts[:2])
  405. l2 = Line(*pts[2:])
  406. # check to see if we're parallel. If we are, we can't
  407. # be intersecting, since the collinear case was already
  408. # handled
  409. if l1.direction.is_scalar_multiple(l2.direction):
  410. return []
  411. # find the intersection as if everything were lines
  412. # by solving the equation t*d + p1 == s*d' + p1'
  413. m = Matrix([l1.direction, -l2.direction]).transpose()
  414. v = Matrix([l2.p1 - l1.p1]).transpose()
  415. # we cannot use m.solve(v) because that only works for square matrices
  416. m_rref, pivots = m.col_insert(2, v).rref(simplify=True)
  417. # rank == 2 ensures we have 2 pivots, but let's check anyway
  418. if len(pivots) != 2:
  419. raise GeometryError("Failed when solving Mx=b when M={} and b={}".format(m, v))
  420. coeff = m_rref[0, 2]
  421. line_intersection = l1.direction*coeff + self.p1
  422. # if we're both lines, we can skip a containment check
  423. if isinstance(self, Line) and isinstance(other, Line):
  424. return [line_intersection]
  425. if ((isinstance(self, Line) or
  426. self.contains(line_intersection)) and
  427. other.contains(line_intersection)):
  428. return [line_intersection]
  429. return []
  430. else:
  431. # we're skew
  432. return []
  433. return other.intersection(self)
  434. def is_parallel(l1, l2):
  435. """Are two linear entities parallel?
  436. Parameters
  437. ==========
  438. l1 : LinearEntity
  439. l2 : LinearEntity
  440. Returns
  441. =======
  442. True : if l1 and l2 are parallel,
  443. False : otherwise.
  444. See Also
  445. ========
  446. coefficients
  447. Examples
  448. ========
  449. >>> from sympy import Point, Line
  450. >>> p1, p2 = Point(0, 0), Point(1, 1)
  451. >>> p3, p4 = Point(3, 4), Point(6, 7)
  452. >>> l1, l2 = Line(p1, p2), Line(p3, p4)
  453. >>> Line.is_parallel(l1, l2)
  454. True
  455. >>> p5 = Point(6, 6)
  456. >>> l3 = Line(p3, p5)
  457. >>> Line.is_parallel(l1, l3)
  458. False
  459. >>> from sympy import Point3D, Line3D
  460. >>> p1, p2 = Point3D(0, 0, 0), Point3D(3, 4, 5)
  461. >>> p3, p4 = Point3D(2, 1, 1), Point3D(8, 9, 11)
  462. >>> l1, l2 = Line3D(p1, p2), Line3D(p3, p4)
  463. >>> Line3D.is_parallel(l1, l2)
  464. True
  465. >>> p5 = Point3D(6, 6, 6)
  466. >>> l3 = Line3D(p3, p5)
  467. >>> Line3D.is_parallel(l1, l3)
  468. False
  469. """
  470. if not isinstance(l1, LinearEntity) and not isinstance(l2, LinearEntity):
  471. raise TypeError('Must pass only LinearEntity objects')
  472. return l1.direction.is_scalar_multiple(l2.direction)
  473. def is_perpendicular(l1, l2):
  474. """Are two linear entities perpendicular?
  475. Parameters
  476. ==========
  477. l1 : LinearEntity
  478. l2 : LinearEntity
  479. Returns
  480. =======
  481. True : if l1 and l2 are perpendicular,
  482. False : otherwise.
  483. See Also
  484. ========
  485. coefficients
  486. Examples
  487. ========
  488. >>> from sympy import Point, Line
  489. >>> p1, p2, p3 = Point(0, 0), Point(1, 1), Point(-1, 1)
  490. >>> l1, l2 = Line(p1, p2), Line(p1, p3)
  491. >>> l1.is_perpendicular(l2)
  492. True
  493. >>> p4 = Point(5, 3)
  494. >>> l3 = Line(p1, p4)
  495. >>> l1.is_perpendicular(l3)
  496. False
  497. >>> from sympy import Point3D, Line3D
  498. >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(1, 1, 1), Point3D(-1, 2, 0)
  499. >>> l1, l2 = Line3D(p1, p2), Line3D(p2, p3)
  500. >>> l1.is_perpendicular(l2)
  501. False
  502. >>> p4 = Point3D(5, 3, 7)
  503. >>> l3 = Line3D(p1, p4)
  504. >>> l1.is_perpendicular(l3)
  505. False
  506. """
  507. if not isinstance(l1, LinearEntity) and not isinstance(l2, LinearEntity):
  508. raise TypeError('Must pass only LinearEntity objects')
  509. return S.Zero.equals(l1.direction.dot(l2.direction))
  510. def is_similar(self, other):
  511. """
  512. Return True if self and other are contained in the same line.
  513. Examples
  514. ========
  515. >>> from sympy import Point, Line
  516. >>> p1, p2, p3 = Point(0, 1), Point(3, 4), Point(2, 3)
  517. >>> l1 = Line(p1, p2)
  518. >>> l2 = Line(p1, p3)
  519. >>> l1.is_similar(l2)
  520. True
  521. """
  522. l = Line(self.p1, self.p2)
  523. return l.contains(other)
  524. @property
  525. def length(self):
  526. """
  527. The length of the line.
  528. Examples
  529. ========
  530. >>> from sympy import Point, Line
  531. >>> p1, p2 = Point(0, 0), Point(3, 5)
  532. >>> l1 = Line(p1, p2)
  533. >>> l1.length
  534. oo
  535. """
  536. return S.Infinity
  537. @property
  538. def p1(self):
  539. """The first defining point of a linear entity.
  540. See Also
  541. ========
  542. sympy.geometry.point.Point
  543. Examples
  544. ========
  545. >>> from sympy import Point, Line
  546. >>> p1, p2 = Point(0, 0), Point(5, 3)
  547. >>> l = Line(p1, p2)
  548. >>> l.p1
  549. Point2D(0, 0)
  550. """
  551. return self.args[0]
  552. @property
  553. def p2(self):
  554. """The second defining point of a linear entity.
  555. See Also
  556. ========
  557. sympy.geometry.point.Point
  558. Examples
  559. ========
  560. >>> from sympy import Point, Line
  561. >>> p1, p2 = Point(0, 0), Point(5, 3)
  562. >>> l = Line(p1, p2)
  563. >>> l.p2
  564. Point2D(5, 3)
  565. """
  566. return self.args[1]
  567. def parallel_line(self, p):
  568. """Create a new Line parallel to this linear entity which passes
  569. through the point `p`.
  570. Parameters
  571. ==========
  572. p : Point
  573. Returns
  574. =======
  575. line : Line
  576. See Also
  577. ========
  578. is_parallel
  579. Examples
  580. ========
  581. >>> from sympy import Point, Line
  582. >>> p1, p2, p3 = Point(0, 0), Point(2, 3), Point(-2, 2)
  583. >>> l1 = Line(p1, p2)
  584. >>> l2 = l1.parallel_line(p3)
  585. >>> p3 in l2
  586. True
  587. >>> l1.is_parallel(l2)
  588. True
  589. >>> from sympy import Point3D, Line3D
  590. >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(2, 3, 4), Point3D(-2, 2, 0)
  591. >>> l1 = Line3D(p1, p2)
  592. >>> l2 = l1.parallel_line(p3)
  593. >>> p3 in l2
  594. True
  595. >>> l1.is_parallel(l2)
  596. True
  597. """
  598. p = Point(p, dim=self.ambient_dimension)
  599. return Line(p, p + self.direction)
  600. def perpendicular_line(self, p):
  601. """Create a new Line perpendicular to this linear entity which passes
  602. through the point `p`.
  603. Parameters
  604. ==========
  605. p : Point
  606. Returns
  607. =======
  608. line : Line
  609. See Also
  610. ========
  611. sympy.geometry.line.LinearEntity.is_perpendicular, perpendicular_segment
  612. Examples
  613. ========
  614. >>> from sympy import Point, Line
  615. >>> p1, p2, p3 = Point(0, 0), Point(2, 3), Point(-2, 2)
  616. >>> l1 = Line(p1, p2)
  617. >>> l2 = l1.perpendicular_line(p3)
  618. >>> p3 in l2
  619. True
  620. >>> l1.is_perpendicular(l2)
  621. True
  622. >>> from sympy import Point3D, Line3D
  623. >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(2, 3, 4), Point3D(-2, 2, 0)
  624. >>> l1 = Line3D(p1, p2)
  625. >>> l2 = l1.perpendicular_line(p3)
  626. >>> p3 in l2
  627. True
  628. >>> l1.is_perpendicular(l2)
  629. True
  630. """
  631. p = Point(p, dim=self.ambient_dimension)
  632. if p in self:
  633. p = p + self.direction.orthogonal_direction
  634. return Line(p, self.projection(p))
  635. def perpendicular_segment(self, p):
  636. """Create a perpendicular line segment from `p` to this line.
  637. The enpoints of the segment are ``p`` and the closest point in
  638. the line containing self. (If self is not a line, the point might
  639. not be in self.)
  640. Parameters
  641. ==========
  642. p : Point
  643. Returns
  644. =======
  645. segment : Segment
  646. Notes
  647. =====
  648. Returns `p` itself if `p` is on this linear entity.
  649. See Also
  650. ========
  651. perpendicular_line
  652. Examples
  653. ========
  654. >>> from sympy import Point, Line
  655. >>> p1, p2, p3 = Point(0, 0), Point(1, 1), Point(0, 2)
  656. >>> l1 = Line(p1, p2)
  657. >>> s1 = l1.perpendicular_segment(p3)
  658. >>> l1.is_perpendicular(s1)
  659. True
  660. >>> p3 in s1
  661. True
  662. >>> l1.perpendicular_segment(Point(4, 0))
  663. Segment2D(Point2D(4, 0), Point2D(2, 2))
  664. >>> from sympy import Point3D, Line3D
  665. >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(1, 1, 1), Point3D(0, 2, 0)
  666. >>> l1 = Line3D(p1, p2)
  667. >>> s1 = l1.perpendicular_segment(p3)
  668. >>> l1.is_perpendicular(s1)
  669. True
  670. >>> p3 in s1
  671. True
  672. >>> l1.perpendicular_segment(Point3D(4, 0, 0))
  673. Segment3D(Point3D(4, 0, 0), Point3D(4/3, 4/3, 4/3))
  674. """
  675. p = Point(p, dim=self.ambient_dimension)
  676. if p in self:
  677. return p
  678. l = self.perpendicular_line(p)
  679. # The intersection should be unique, so unpack the singleton
  680. p2, = Intersection(Line(self.p1, self.p2), l)
  681. return Segment(p, p2)
  682. @property
  683. def points(self):
  684. """The two points used to define this linear entity.
  685. Returns
  686. =======
  687. points : tuple of Points
  688. See Also
  689. ========
  690. sympy.geometry.point.Point
  691. Examples
  692. ========
  693. >>> from sympy import Point, Line
  694. >>> p1, p2 = Point(0, 0), Point(5, 11)
  695. >>> l1 = Line(p1, p2)
  696. >>> l1.points
  697. (Point2D(0, 0), Point2D(5, 11))
  698. """
  699. return (self.p1, self.p2)
  700. def projection(self, other):
  701. """Project a point, line, ray, or segment onto this linear entity.
  702. Parameters
  703. ==========
  704. other : Point or LinearEntity (Line, Ray, Segment)
  705. Returns
  706. =======
  707. projection : Point or LinearEntity (Line, Ray, Segment)
  708. The return type matches the type of the parameter ``other``.
  709. Raises
  710. ======
  711. GeometryError
  712. When method is unable to perform projection.
  713. Notes
  714. =====
  715. A projection involves taking the two points that define
  716. the linear entity and projecting those points onto a
  717. Line and then reforming the linear entity using these
  718. projections.
  719. A point P is projected onto a line L by finding the point
  720. on L that is closest to P. This point is the intersection
  721. of L and the line perpendicular to L that passes through P.
  722. See Also
  723. ========
  724. sympy.geometry.point.Point, perpendicular_line
  725. Examples
  726. ========
  727. >>> from sympy import Point, Line, Segment, Rational
  728. >>> p1, p2, p3 = Point(0, 0), Point(1, 1), Point(Rational(1, 2), 0)
  729. >>> l1 = Line(p1, p2)
  730. >>> l1.projection(p3)
  731. Point2D(1/4, 1/4)
  732. >>> p4, p5 = Point(10, 0), Point(12, 1)
  733. >>> s1 = Segment(p4, p5)
  734. >>> l1.projection(s1)
  735. Segment2D(Point2D(5, 5), Point2D(13/2, 13/2))
  736. >>> p1, p2, p3 = Point(0, 0, 1), Point(1, 1, 2), Point(2, 0, 1)
  737. >>> l1 = Line(p1, p2)
  738. >>> l1.projection(p3)
  739. Point3D(2/3, 2/3, 5/3)
  740. >>> p4, p5 = Point(10, 0, 1), Point(12, 1, 3)
  741. >>> s1 = Segment(p4, p5)
  742. >>> l1.projection(s1)
  743. Segment3D(Point3D(10/3, 10/3, 13/3), Point3D(5, 5, 6))
  744. """
  745. if not isinstance(other, GeometryEntity):
  746. other = Point(other, dim=self.ambient_dimension)
  747. def proj_point(p):
  748. return Point.project(p - self.p1, self.direction) + self.p1
  749. if isinstance(other, Point):
  750. return proj_point(other)
  751. elif isinstance(other, LinearEntity):
  752. p1, p2 = proj_point(other.p1), proj_point(other.p2)
  753. # test to see if we're degenerate
  754. if p1 == p2:
  755. return p1
  756. projected = other.__class__(p1, p2)
  757. projected = Intersection(self, projected)
  758. if projected.is_empty:
  759. return projected
  760. # if we happen to have intersected in only a point, return that
  761. if projected.is_FiniteSet and len(projected) == 1:
  762. # projected is a set of size 1, so unpack it in `a`
  763. a, = projected
  764. return a
  765. # order args so projection is in the same direction as self
  766. if self.direction.dot(projected.direction) < 0:
  767. p1, p2 = projected.args
  768. projected = projected.func(p2, p1)
  769. return projected
  770. raise GeometryError(
  771. "Do not know how to project %s onto %s" % (other, self))
  772. def random_point(self, seed=None):
  773. """A random point on a LinearEntity.
  774. Returns
  775. =======
  776. point : Point
  777. See Also
  778. ========
  779. sympy.geometry.point.Point
  780. Examples
  781. ========
  782. >>> from sympy import Point, Line, Ray, Segment
  783. >>> p1, p2 = Point(0, 0), Point(5, 3)
  784. >>> line = Line(p1, p2)
  785. >>> r = line.random_point(seed=42) # seed value is optional
  786. >>> r.n(3)
  787. Point2D(-0.72, -0.432)
  788. >>> r in line
  789. True
  790. >>> Ray(p1, p2).random_point(seed=42).n(3)
  791. Point2D(0.72, 0.432)
  792. >>> Segment(p1, p2).random_point(seed=42).n(3)
  793. Point2D(3.2, 1.92)
  794. """
  795. if seed is not None:
  796. rng = random.Random(seed)
  797. else:
  798. rng = random
  799. t = Dummy()
  800. pt = self.arbitrary_point(t)
  801. if isinstance(self, Ray):
  802. v = abs(rng.gauss(0, 1))
  803. elif isinstance(self, Segment):
  804. v = rng.random()
  805. elif isinstance(self, Line):
  806. v = rng.gauss(0, 1)
  807. else:
  808. raise NotImplementedError('unhandled line type')
  809. return pt.subs(t, Rational(v))
  810. def bisectors(self, other):
  811. """Returns the perpendicular lines which pass through the intersections
  812. of self and other that are in the same plane.
  813. Parameters
  814. ==========
  815. line : Line3D
  816. Returns
  817. =======
  818. list: two Line instances
  819. Examples
  820. ========
  821. >>> from sympy import Point3D, Line3D
  822. >>> r1 = Line3D(Point3D(0, 0, 0), Point3D(1, 0, 0))
  823. >>> r2 = Line3D(Point3D(0, 0, 0), Point3D(0, 1, 0))
  824. >>> r1.bisectors(r2)
  825. [Line3D(Point3D(0, 0, 0), Point3D(1, 1, 0)), Line3D(Point3D(0, 0, 0), Point3D(1, -1, 0))]
  826. """
  827. if not isinstance(other, LinearEntity):
  828. raise GeometryError("Expecting LinearEntity, not %s" % other)
  829. l1, l2 = self, other
  830. # make sure dimensions match or else a warning will rise from
  831. # intersection calculation
  832. if l1.p1.ambient_dimension != l2.p1.ambient_dimension:
  833. if isinstance(l1, Line2D):
  834. l1, l2 = l2, l1
  835. _, p1 = Point._normalize_dimension(l1.p1, l2.p1, on_morph='ignore')
  836. _, p2 = Point._normalize_dimension(l1.p2, l2.p2, on_morph='ignore')
  837. l2 = Line(p1, p2)
  838. point = intersection(l1, l2)
  839. # Three cases: Lines may intersect in a point, may be equal or may not intersect.
  840. if not point:
  841. raise GeometryError("The lines do not intersect")
  842. else:
  843. pt = point[0]
  844. if isinstance(pt, Line):
  845. # Intersection is a line because both lines are coincident
  846. return [self]
  847. d1 = l1.direction.unit
  848. d2 = l2.direction.unit
  849. bis1 = Line(pt, pt + d1 + d2)
  850. bis2 = Line(pt, pt + d1 - d2)
  851. return [bis1, bis2]
  852. class Line(LinearEntity):
  853. """An infinite line in space.
  854. A 2D line is declared with two distinct points, point and slope, or
  855. an equation. A 3D line may be defined with a point and a direction ratio.
  856. Parameters
  857. ==========
  858. p1 : Point
  859. p2 : Point
  860. slope : SymPy expression
  861. direction_ratio : list
  862. equation : equation of a line
  863. Notes
  864. =====
  865. `Line` will automatically subclass to `Line2D` or `Line3D` based
  866. on the dimension of `p1`. The `slope` argument is only relevant
  867. for `Line2D` and the `direction_ratio` argument is only relevant
  868. for `Line3D`.
  869. See Also
  870. ========
  871. sympy.geometry.point.Point
  872. sympy.geometry.line.Line2D
  873. sympy.geometry.line.Line3D
  874. Examples
  875. ========
  876. >>> from sympy import Line, Segment, Point, Eq
  877. >>> from sympy.abc import x, y, a, b
  878. >>> L = Line(Point(2,3), Point(3,5))
  879. >>> L
  880. Line2D(Point2D(2, 3), Point2D(3, 5))
  881. >>> L.points
  882. (Point2D(2, 3), Point2D(3, 5))
  883. >>> L.equation()
  884. -2*x + y + 1
  885. >>> L.coefficients
  886. (-2, 1, 1)
  887. Instantiate with keyword ``slope``:
  888. >>> Line(Point(0, 0), slope=0)
  889. Line2D(Point2D(0, 0), Point2D(1, 0))
  890. Instantiate with another linear object
  891. >>> s = Segment((0, 0), (0, 1))
  892. >>> Line(s).equation()
  893. x
  894. The line corresponding to an equation in the for `ax + by + c = 0`,
  895. can be entered:
  896. >>> Line(3*x + y + 18)
  897. Line2D(Point2D(0, -18), Point2D(1, -21))
  898. If `x` or `y` has a different name, then they can be specified, too,
  899. as a string (to match the name) or symbol:
  900. >>> Line(Eq(3*a + b, -18), x='a', y=b)
  901. Line2D(Point2D(0, -18), Point2D(1, -21))
  902. """
  903. def __new__(cls, *args, **kwargs):
  904. if len(args) == 1 and isinstance(args[0], (Expr, Eq)):
  905. missing = uniquely_named_symbol('?', args)
  906. if not kwargs:
  907. x = 'x'
  908. y = 'y'
  909. else:
  910. x = kwargs.pop('x', missing)
  911. y = kwargs.pop('y', missing)
  912. if kwargs:
  913. raise ValueError('expecting only x and y as keywords')
  914. equation = args[0]
  915. if isinstance(equation, Eq):
  916. equation = equation.lhs - equation.rhs
  917. def find_or_missing(x):
  918. try:
  919. return find(x, equation)
  920. except ValueError:
  921. return missing
  922. x = find_or_missing(x)
  923. y = find_or_missing(y)
  924. a, b, c = linear_coeffs(equation, x, y)
  925. if b:
  926. return Line((0, -c/b), slope=-a/b)
  927. if a:
  928. return Line((-c/a, 0), slope=oo)
  929. raise ValueError('not found in equation: %s' % (set('xy') - {x, y}))
  930. else:
  931. if len(args) > 0:
  932. p1 = args[0]
  933. if len(args) > 1:
  934. p2 = args[1]
  935. else:
  936. p2 = None
  937. if isinstance(p1, LinearEntity):
  938. if p2:
  939. raise ValueError('If p1 is a LinearEntity, p2 must be None.')
  940. dim = len(p1.p1)
  941. else:
  942. p1 = Point(p1)
  943. dim = len(p1)
  944. if p2 is not None or isinstance(p2, Point) and p2.ambient_dimension != dim:
  945. p2 = Point(p2)
  946. if dim == 2:
  947. return Line2D(p1, p2, **kwargs)
  948. elif dim == 3:
  949. return Line3D(p1, p2, **kwargs)
  950. return LinearEntity.__new__(cls, p1, p2, **kwargs)
  951. def contains(self, other):
  952. """
  953. Return True if `other` is on this Line, or False otherwise.
  954. Examples
  955. ========
  956. >>> from sympy import Line,Point
  957. >>> p1, p2 = Point(0, 1), Point(3, 4)
  958. >>> l = Line(p1, p2)
  959. >>> l.contains(p1)
  960. True
  961. >>> l.contains((0, 1))
  962. True
  963. >>> l.contains((0, 0))
  964. False
  965. >>> a = (0, 0, 0)
  966. >>> b = (1, 1, 1)
  967. >>> c = (2, 2, 2)
  968. >>> l1 = Line(a, b)
  969. >>> l2 = Line(b, a)
  970. >>> l1 == l2
  971. False
  972. >>> l1 in l2
  973. True
  974. """
  975. if not isinstance(other, GeometryEntity):
  976. other = Point(other, dim=self.ambient_dimension)
  977. if isinstance(other, Point):
  978. return Point.is_collinear(other, self.p1, self.p2)
  979. if isinstance(other, LinearEntity):
  980. return Point.is_collinear(self.p1, self.p2, other.p1, other.p2)
  981. return False
  982. def distance(self, other):
  983. """
  984. Finds the shortest distance between a line and a point.
  985. Raises
  986. ======
  987. NotImplementedError is raised if `other` is not a Point
  988. Examples
  989. ========
  990. >>> from sympy import Point, Line
  991. >>> p1, p2 = Point(0, 0), Point(1, 1)
  992. >>> s = Line(p1, p2)
  993. >>> s.distance(Point(-1, 1))
  994. sqrt(2)
  995. >>> s.distance((-1, 2))
  996. 3*sqrt(2)/2
  997. >>> p1, p2 = Point(0, 0, 0), Point(1, 1, 1)
  998. >>> s = Line(p1, p2)
  999. >>> s.distance(Point(-1, 1, 1))
  1000. 2*sqrt(6)/3
  1001. >>> s.distance((-1, 1, 1))
  1002. 2*sqrt(6)/3
  1003. """
  1004. if not isinstance(other, GeometryEntity):
  1005. other = Point(other, dim=self.ambient_dimension)
  1006. if self.contains(other):
  1007. return S.Zero
  1008. return self.perpendicular_segment(other).length
  1009. def equals(self, other):
  1010. """Returns True if self and other are the same mathematical entities"""
  1011. if not isinstance(other, Line):
  1012. return False
  1013. return Point.is_collinear(self.p1, other.p1, self.p2, other.p2)
  1014. def plot_interval(self, parameter='t'):
  1015. """The plot interval for the default geometric plot of line. Gives
  1016. values that will produce a line that is +/- 5 units long (where a
  1017. unit is the distance between the two points that define the line).
  1018. Parameters
  1019. ==========
  1020. parameter : str, optional
  1021. Default value is 't'.
  1022. Returns
  1023. =======
  1024. plot_interval : list (plot interval)
  1025. [parameter, lower_bound, upper_bound]
  1026. Examples
  1027. ========
  1028. >>> from sympy import Point, Line
  1029. >>> p1, p2 = Point(0, 0), Point(5, 3)
  1030. >>> l1 = Line(p1, p2)
  1031. >>> l1.plot_interval()
  1032. [t, -5, 5]
  1033. """
  1034. t = _symbol(parameter, real=True)
  1035. return [t, -5, 5]
  1036. class Ray(LinearEntity):
  1037. """A Ray is a semi-line in the space with a source point and a direction.
  1038. Parameters
  1039. ==========
  1040. p1 : Point
  1041. The source of the Ray
  1042. p2 : Point or radian value
  1043. This point determines the direction in which the Ray propagates.
  1044. If given as an angle it is interpreted in radians with the positive
  1045. direction being ccw.
  1046. Attributes
  1047. ==========
  1048. source
  1049. See Also
  1050. ========
  1051. sympy.geometry.line.Ray2D
  1052. sympy.geometry.line.Ray3D
  1053. sympy.geometry.point.Point
  1054. sympy.geometry.line.Line
  1055. Notes
  1056. =====
  1057. `Ray` will automatically subclass to `Ray2D` or `Ray3D` based on the
  1058. dimension of `p1`.
  1059. Examples
  1060. ========
  1061. >>> from sympy import Ray, Point, pi
  1062. >>> r = Ray(Point(2, 3), Point(3, 5))
  1063. >>> r
  1064. Ray2D(Point2D(2, 3), Point2D(3, 5))
  1065. >>> r.points
  1066. (Point2D(2, 3), Point2D(3, 5))
  1067. >>> r.source
  1068. Point2D(2, 3)
  1069. >>> r.xdirection
  1070. oo
  1071. >>> r.ydirection
  1072. oo
  1073. >>> r.slope
  1074. 2
  1075. >>> Ray(Point(0, 0), angle=pi/4).slope
  1076. 1
  1077. """
  1078. def __new__(cls, p1, p2=None, **kwargs):
  1079. p1 = Point(p1)
  1080. if p2 is not None:
  1081. p1, p2 = Point._normalize_dimension(p1, Point(p2))
  1082. dim = len(p1)
  1083. if dim == 2:
  1084. return Ray2D(p1, p2, **kwargs)
  1085. elif dim == 3:
  1086. return Ray3D(p1, p2, **kwargs)
  1087. return LinearEntity.__new__(cls, p1, p2, **kwargs)
  1088. def _svg(self, scale_factor=1., fill_color="#66cc99"):
  1089. """Returns SVG path element for the LinearEntity.
  1090. Parameters
  1091. ==========
  1092. scale_factor : float
  1093. Multiplication factor for the SVG stroke-width. Default is 1.
  1094. fill_color : str, optional
  1095. Hex string for fill color. Default is "#66cc99".
  1096. """
  1097. verts = (N(self.p1), N(self.p2))
  1098. coords = ["{},{}".format(p.x, p.y) for p in verts]
  1099. path = "M {} L {}".format(coords[0], " L ".join(coords[1:]))
  1100. return (
  1101. '<path fill-rule="evenodd" fill="{2}" stroke="#555555" '
  1102. 'stroke-width="{0}" opacity="0.6" d="{1}" '
  1103. 'marker-start="url(#markerCircle)" marker-end="url(#markerArrow)"/>'
  1104. ).format(2.*scale_factor, path, fill_color)
  1105. def contains(self, other):
  1106. """
  1107. Is other GeometryEntity contained in this Ray?
  1108. Examples
  1109. ========
  1110. >>> from sympy import Ray,Point,Segment
  1111. >>> p1, p2 = Point(0, 0), Point(4, 4)
  1112. >>> r = Ray(p1, p2)
  1113. >>> r.contains(p1)
  1114. True
  1115. >>> r.contains((1, 1))
  1116. True
  1117. >>> r.contains((1, 3))
  1118. False
  1119. >>> s = Segment((1, 1), (2, 2))
  1120. >>> r.contains(s)
  1121. True
  1122. >>> s = Segment((1, 2), (2, 5))
  1123. >>> r.contains(s)
  1124. False
  1125. >>> r1 = Ray((2, 2), (3, 3))
  1126. >>> r.contains(r1)
  1127. True
  1128. >>> r1 = Ray((2, 2), (3, 5))
  1129. >>> r.contains(r1)
  1130. False
  1131. """
  1132. if not isinstance(other, GeometryEntity):
  1133. other = Point(other, dim=self.ambient_dimension)
  1134. if isinstance(other, Point):
  1135. if Point.is_collinear(self.p1, self.p2, other):
  1136. # if we're in the direction of the ray, our
  1137. # direction vector dot the ray's direction vector
  1138. # should be non-negative
  1139. return bool((self.p2 - self.p1).dot(other - self.p1) >= S.Zero)
  1140. return False
  1141. elif isinstance(other, Ray):
  1142. if Point.is_collinear(self.p1, self.p2, other.p1, other.p2):
  1143. return bool((self.p2 - self.p1).dot(other.p2 - other.p1) > S.Zero)
  1144. return False
  1145. elif isinstance(other, Segment):
  1146. return other.p1 in self and other.p2 in self
  1147. # No other known entity can be contained in a Ray
  1148. return False
  1149. def distance(self, other):
  1150. """
  1151. Finds the shortest distance between the ray and a point.
  1152. Raises
  1153. ======
  1154. NotImplementedError is raised if `other` is not a Point
  1155. Examples
  1156. ========
  1157. >>> from sympy import Point, Ray
  1158. >>> p1, p2 = Point(0, 0), Point(1, 1)
  1159. >>> s = Ray(p1, p2)
  1160. >>> s.distance(Point(-1, -1))
  1161. sqrt(2)
  1162. >>> s.distance((-1, 2))
  1163. 3*sqrt(2)/2
  1164. >>> p1, p2 = Point(0, 0, 0), Point(1, 1, 2)
  1165. >>> s = Ray(p1, p2)
  1166. >>> s
  1167. Ray3D(Point3D(0, 0, 0), Point3D(1, 1, 2))
  1168. >>> s.distance(Point(-1, -1, 2))
  1169. 4*sqrt(3)/3
  1170. >>> s.distance((-1, -1, 2))
  1171. 4*sqrt(3)/3
  1172. """
  1173. if not isinstance(other, GeometryEntity):
  1174. other = Point(other, dim=self.ambient_dimension)
  1175. if self.contains(other):
  1176. return S.Zero
  1177. proj = Line(self.p1, self.p2).projection(other)
  1178. if self.contains(proj):
  1179. return abs(other - proj)
  1180. else:
  1181. return abs(other - self.source)
  1182. def equals(self, other):
  1183. """Returns True if self and other are the same mathematical entities"""
  1184. if not isinstance(other, Ray):
  1185. return False
  1186. return self.source == other.source and other.p2 in self
  1187. def plot_interval(self, parameter='t'):
  1188. """The plot interval for the default geometric plot of the Ray. Gives
  1189. values that will produce a ray that is 10 units long (where a unit is
  1190. the distance between the two points that define the ray).
  1191. Parameters
  1192. ==========
  1193. parameter : str, optional
  1194. Default value is 't'.
  1195. Returns
  1196. =======
  1197. plot_interval : list
  1198. [parameter, lower_bound, upper_bound]
  1199. Examples
  1200. ========
  1201. >>> from sympy import Ray, pi
  1202. >>> r = Ray((0, 0), angle=pi/4)
  1203. >>> r.plot_interval()
  1204. [t, 0, 10]
  1205. """
  1206. t = _symbol(parameter, real=True)
  1207. return [t, 0, 10]
  1208. @property
  1209. def source(self):
  1210. """The point from which the ray emanates.
  1211. See Also
  1212. ========
  1213. sympy.geometry.point.Point
  1214. Examples
  1215. ========
  1216. >>> from sympy import Point, Ray
  1217. >>> p1, p2 = Point(0, 0), Point(4, 1)
  1218. >>> r1 = Ray(p1, p2)
  1219. >>> r1.source
  1220. Point2D(0, 0)
  1221. >>> p1, p2 = Point(0, 0, 0), Point(4, 1, 5)
  1222. >>> r1 = Ray(p2, p1)
  1223. >>> r1.source
  1224. Point3D(4, 1, 5)
  1225. """
  1226. return self.p1
  1227. class Segment(LinearEntity):
  1228. """A line segment in space.
  1229. Parameters
  1230. ==========
  1231. p1 : Point
  1232. p2 : Point
  1233. Attributes
  1234. ==========
  1235. length : number or SymPy expression
  1236. midpoint : Point
  1237. See Also
  1238. ========
  1239. sympy.geometry.line.Segment2D
  1240. sympy.geometry.line.Segment3D
  1241. sympy.geometry.point.Point
  1242. sympy.geometry.line.Line
  1243. Notes
  1244. =====
  1245. If 2D or 3D points are used to define `Segment`, it will
  1246. be automatically subclassed to `Segment2D` or `Segment3D`.
  1247. Examples
  1248. ========
  1249. >>> from sympy import Point, Segment
  1250. >>> Segment((1, 0), (1, 1)) # tuples are interpreted as pts
  1251. Segment2D(Point2D(1, 0), Point2D(1, 1))
  1252. >>> s = Segment(Point(4, 3), Point(1, 1))
  1253. >>> s.points
  1254. (Point2D(4, 3), Point2D(1, 1))
  1255. >>> s.slope
  1256. 2/3
  1257. >>> s.length
  1258. sqrt(13)
  1259. >>> s.midpoint
  1260. Point2D(5/2, 2)
  1261. >>> Segment((1, 0, 0), (1, 1, 1)) # tuples are interpreted as pts
  1262. Segment3D(Point3D(1, 0, 0), Point3D(1, 1, 1))
  1263. >>> s = Segment(Point(4, 3, 9), Point(1, 1, 7)); s
  1264. Segment3D(Point3D(4, 3, 9), Point3D(1, 1, 7))
  1265. >>> s.points
  1266. (Point3D(4, 3, 9), Point3D(1, 1, 7))
  1267. >>> s.length
  1268. sqrt(17)
  1269. >>> s.midpoint
  1270. Point3D(5/2, 2, 8)
  1271. """
  1272. def __new__(cls, p1, p2, **kwargs):
  1273. p1, p2 = Point._normalize_dimension(Point(p1), Point(p2))
  1274. dim = len(p1)
  1275. if dim == 2:
  1276. return Segment2D(p1, p2, **kwargs)
  1277. elif dim == 3:
  1278. return Segment3D(p1, p2, **kwargs)
  1279. return LinearEntity.__new__(cls, p1, p2, **kwargs)
  1280. def contains(self, other):
  1281. """
  1282. Is the other GeometryEntity contained within this Segment?
  1283. Examples
  1284. ========
  1285. >>> from sympy import Point, Segment
  1286. >>> p1, p2 = Point(0, 1), Point(3, 4)
  1287. >>> s = Segment(p1, p2)
  1288. >>> s2 = Segment(p2, p1)
  1289. >>> s.contains(s2)
  1290. True
  1291. >>> from sympy import Point3D, Segment3D
  1292. >>> p1, p2 = Point3D(0, 1, 1), Point3D(3, 4, 5)
  1293. >>> s = Segment3D(p1, p2)
  1294. >>> s2 = Segment3D(p2, p1)
  1295. >>> s.contains(s2)
  1296. True
  1297. >>> s.contains((p1 + p2)/2)
  1298. True
  1299. """
  1300. if not isinstance(other, GeometryEntity):
  1301. other = Point(other, dim=self.ambient_dimension)
  1302. if isinstance(other, Point):
  1303. if Point.is_collinear(other, self.p1, self.p2):
  1304. if isinstance(self, Segment2D):
  1305. # if it is collinear and is in the bounding box of the
  1306. # segment then it must be on the segment
  1307. vert = (1/self.slope).equals(0)
  1308. if vert is False:
  1309. isin = (self.p1.x - other.x)*(self.p2.x - other.x) <= 0
  1310. if isin in (True, False):
  1311. return isin
  1312. if vert is True:
  1313. isin = (self.p1.y - other.y)*(self.p2.y - other.y) <= 0
  1314. if isin in (True, False):
  1315. return isin
  1316. # use the triangle inequality
  1317. d1, d2 = other - self.p1, other - self.p2
  1318. d = self.p2 - self.p1
  1319. # without the call to simplify, SymPy cannot tell that an expression
  1320. # like (a+b)*(a/2+b/2) is always non-negative. If it cannot be
  1321. # determined, raise an Undecidable error
  1322. try:
  1323. # the triangle inequality says that |d1|+|d2| >= |d| and is strict
  1324. # only if other lies in the line segment
  1325. return bool(simplify(Eq(abs(d1) + abs(d2) - abs(d), 0)))
  1326. except TypeError:
  1327. raise Undecidable("Cannot determine if {} is in {}".format(other, self))
  1328. if isinstance(other, Segment):
  1329. return other.p1 in self and other.p2 in self
  1330. return False
  1331. def equals(self, other):
  1332. """Returns True if self and other are the same mathematical entities"""
  1333. return isinstance(other, self.func) and list(
  1334. ordered(self.args)) == list(ordered(other.args))
  1335. def distance(self, other):
  1336. """
  1337. Finds the shortest distance between a line segment and a point.
  1338. Raises
  1339. ======
  1340. NotImplementedError is raised if `other` is not a Point
  1341. Examples
  1342. ========
  1343. >>> from sympy import Point, Segment
  1344. >>> p1, p2 = Point(0, 1), Point(3, 4)
  1345. >>> s = Segment(p1, p2)
  1346. >>> s.distance(Point(10, 15))
  1347. sqrt(170)
  1348. >>> s.distance((0, 12))
  1349. sqrt(73)
  1350. >>> from sympy import Point3D, Segment3D
  1351. >>> p1, p2 = Point3D(0, 0, 3), Point3D(1, 1, 4)
  1352. >>> s = Segment3D(p1, p2)
  1353. >>> s.distance(Point3D(10, 15, 12))
  1354. sqrt(341)
  1355. >>> s.distance((10, 15, 12))
  1356. sqrt(341)
  1357. """
  1358. if not isinstance(other, GeometryEntity):
  1359. other = Point(other, dim=self.ambient_dimension)
  1360. if isinstance(other, Point):
  1361. vp1 = other - self.p1
  1362. vp2 = other - self.p2
  1363. dot_prod_sign_1 = self.direction.dot(vp1) >= 0
  1364. dot_prod_sign_2 = self.direction.dot(vp2) <= 0
  1365. if dot_prod_sign_1 and dot_prod_sign_2:
  1366. return Line(self.p1, self.p2).distance(other)
  1367. if dot_prod_sign_1 and not dot_prod_sign_2:
  1368. return abs(vp2)
  1369. if not dot_prod_sign_1 and dot_prod_sign_2:
  1370. return abs(vp1)
  1371. raise NotImplementedError()
  1372. @property
  1373. def length(self):
  1374. """The length of the line segment.
  1375. See Also
  1376. ========
  1377. sympy.geometry.point.Point.distance
  1378. Examples
  1379. ========
  1380. >>> from sympy import Point, Segment
  1381. >>> p1, p2 = Point(0, 0), Point(4, 3)
  1382. >>> s1 = Segment(p1, p2)
  1383. >>> s1.length
  1384. 5
  1385. >>> from sympy import Point3D, Segment3D
  1386. >>> p1, p2 = Point3D(0, 0, 0), Point3D(4, 3, 3)
  1387. >>> s1 = Segment3D(p1, p2)
  1388. >>> s1.length
  1389. sqrt(34)
  1390. """
  1391. return Point.distance(self.p1, self.p2)
  1392. @property
  1393. def midpoint(self):
  1394. """The midpoint of the line segment.
  1395. See Also
  1396. ========
  1397. sympy.geometry.point.Point.midpoint
  1398. Examples
  1399. ========
  1400. >>> from sympy import Point, Segment
  1401. >>> p1, p2 = Point(0, 0), Point(4, 3)
  1402. >>> s1 = Segment(p1, p2)
  1403. >>> s1.midpoint
  1404. Point2D(2, 3/2)
  1405. >>> from sympy import Point3D, Segment3D
  1406. >>> p1, p2 = Point3D(0, 0, 0), Point3D(4, 3, 3)
  1407. >>> s1 = Segment3D(p1, p2)
  1408. >>> s1.midpoint
  1409. Point3D(2, 3/2, 3/2)
  1410. """
  1411. return Point.midpoint(self.p1, self.p2)
  1412. def perpendicular_bisector(self, p=None):
  1413. """The perpendicular bisector of this segment.
  1414. If no point is specified or the point specified is not on the
  1415. bisector then the bisector is returned as a Line. Otherwise a
  1416. Segment is returned that joins the point specified and the
  1417. intersection of the bisector and the segment.
  1418. Parameters
  1419. ==========
  1420. p : Point
  1421. Returns
  1422. =======
  1423. bisector : Line or Segment
  1424. See Also
  1425. ========
  1426. LinearEntity.perpendicular_segment
  1427. Examples
  1428. ========
  1429. >>> from sympy import Point, Segment
  1430. >>> p1, p2, p3 = Point(0, 0), Point(6, 6), Point(5, 1)
  1431. >>> s1 = Segment(p1, p2)
  1432. >>> s1.perpendicular_bisector()
  1433. Line2D(Point2D(3, 3), Point2D(-3, 9))
  1434. >>> s1.perpendicular_bisector(p3)
  1435. Segment2D(Point2D(5, 1), Point2D(3, 3))
  1436. """
  1437. l = self.perpendicular_line(self.midpoint)
  1438. if p is not None:
  1439. p2 = Point(p, dim=self.ambient_dimension)
  1440. if p2 in l:
  1441. return Segment(p2, self.midpoint)
  1442. return l
  1443. def plot_interval(self, parameter='t'):
  1444. """The plot interval for the default geometric plot of the Segment gives
  1445. values that will produce the full segment in a plot.
  1446. Parameters
  1447. ==========
  1448. parameter : str, optional
  1449. Default value is 't'.
  1450. Returns
  1451. =======
  1452. plot_interval : list
  1453. [parameter, lower_bound, upper_bound]
  1454. Examples
  1455. ========
  1456. >>> from sympy import Point, Segment
  1457. >>> p1, p2 = Point(0, 0), Point(5, 3)
  1458. >>> s1 = Segment(p1, p2)
  1459. >>> s1.plot_interval()
  1460. [t, 0, 1]
  1461. """
  1462. t = _symbol(parameter, real=True)
  1463. return [t, 0, 1]
  1464. class LinearEntity2D(LinearEntity):
  1465. """A base class for all linear entities (line, ray and segment)
  1466. in a 2-dimensional Euclidean space.
  1467. Attributes
  1468. ==========
  1469. p1
  1470. p2
  1471. coefficients
  1472. slope
  1473. points
  1474. Notes
  1475. =====
  1476. This is an abstract class and is not meant to be instantiated.
  1477. See Also
  1478. ========
  1479. sympy.geometry.entity.GeometryEntity
  1480. """
  1481. @property
  1482. def bounds(self):
  1483. """Return a tuple (xmin, ymin, xmax, ymax) representing the bounding
  1484. rectangle for the geometric figure.
  1485. """
  1486. verts = self.points
  1487. xs = [p.x for p in verts]
  1488. ys = [p.y for p in verts]
  1489. return (min(xs), min(ys), max(xs), max(ys))
  1490. def perpendicular_line(self, p):
  1491. """Create a new Line perpendicular to this linear entity which passes
  1492. through the point `p`.
  1493. Parameters
  1494. ==========
  1495. p : Point
  1496. Returns
  1497. =======
  1498. line : Line
  1499. See Also
  1500. ========
  1501. sympy.geometry.line.LinearEntity.is_perpendicular, perpendicular_segment
  1502. Examples
  1503. ========
  1504. >>> from sympy import Point, Line
  1505. >>> p1, p2, p3 = Point(0, 0), Point(2, 3), Point(-2, 2)
  1506. >>> l1 = Line(p1, p2)
  1507. >>> l2 = l1.perpendicular_line(p3)
  1508. >>> p3 in l2
  1509. True
  1510. >>> l1.is_perpendicular(l2)
  1511. True
  1512. """
  1513. p = Point(p, dim=self.ambient_dimension)
  1514. # any two lines in R^2 intersect, so blindly making
  1515. # a line through p in an orthogonal direction will work
  1516. return Line(p, p + self.direction.orthogonal_direction)
  1517. @property
  1518. def slope(self):
  1519. """The slope of this linear entity, or infinity if vertical.
  1520. Returns
  1521. =======
  1522. slope : number or SymPy expression
  1523. See Also
  1524. ========
  1525. coefficients
  1526. Examples
  1527. ========
  1528. >>> from sympy import Point, Line
  1529. >>> p1, p2 = Point(0, 0), Point(3, 5)
  1530. >>> l1 = Line(p1, p2)
  1531. >>> l1.slope
  1532. 5/3
  1533. >>> p3 = Point(0, 4)
  1534. >>> l2 = Line(p1, p3)
  1535. >>> l2.slope
  1536. oo
  1537. """
  1538. d1, d2 = (self.p1 - self.p2).args
  1539. if d1 == 0:
  1540. return S.Infinity
  1541. return simplify(d2/d1)
  1542. class Line2D(LinearEntity2D, Line):
  1543. """An infinite line in space 2D.
  1544. A line is declared with two distinct points or a point and slope
  1545. as defined using keyword `slope`.
  1546. Parameters
  1547. ==========
  1548. p1 : Point
  1549. pt : Point
  1550. slope : SymPy expression
  1551. See Also
  1552. ========
  1553. sympy.geometry.point.Point
  1554. Examples
  1555. ========
  1556. >>> from sympy import Line, Segment, Point
  1557. >>> L = Line(Point(2,3), Point(3,5))
  1558. >>> L
  1559. Line2D(Point2D(2, 3), Point2D(3, 5))
  1560. >>> L.points
  1561. (Point2D(2, 3), Point2D(3, 5))
  1562. >>> L.equation()
  1563. -2*x + y + 1
  1564. >>> L.coefficients
  1565. (-2, 1, 1)
  1566. Instantiate with keyword ``slope``:
  1567. >>> Line(Point(0, 0), slope=0)
  1568. Line2D(Point2D(0, 0), Point2D(1, 0))
  1569. Instantiate with another linear object
  1570. >>> s = Segment((0, 0), (0, 1))
  1571. >>> Line(s).equation()
  1572. x
  1573. """
  1574. def __new__(cls, p1, pt=None, slope=None, **kwargs):
  1575. if isinstance(p1, LinearEntity):
  1576. if pt is not None:
  1577. raise ValueError('When p1 is a LinearEntity, pt should be None')
  1578. p1, pt = Point._normalize_dimension(*p1.args, dim=2)
  1579. else:
  1580. p1 = Point(p1, dim=2)
  1581. if pt is not None and slope is None:
  1582. try:
  1583. p2 = Point(pt, dim=2)
  1584. except (NotImplementedError, TypeError, ValueError):
  1585. raise ValueError(filldedent('''
  1586. The 2nd argument was not a valid Point.
  1587. If it was a slope, enter it with keyword "slope".
  1588. '''))
  1589. elif slope is not None and pt is None:
  1590. slope = sympify(slope)
  1591. if slope.is_finite is False:
  1592. # when infinite slope, don't change x
  1593. dx = 0
  1594. dy = 1
  1595. else:
  1596. # go over 1 up slope
  1597. dx = 1
  1598. dy = slope
  1599. # XXX avoiding simplification by adding to coords directly
  1600. p2 = Point(p1.x + dx, p1.y + dy, evaluate=False)
  1601. else:
  1602. raise ValueError('A 2nd Point or keyword "slope" must be used.')
  1603. return LinearEntity2D.__new__(cls, p1, p2, **kwargs)
  1604. def _svg(self, scale_factor=1., fill_color="#66cc99"):
  1605. """Returns SVG path element for the LinearEntity.
  1606. Parameters
  1607. ==========
  1608. scale_factor : float
  1609. Multiplication factor for the SVG stroke-width. Default is 1.
  1610. fill_color : str, optional
  1611. Hex string for fill color. Default is "#66cc99".
  1612. """
  1613. verts = (N(self.p1), N(self.p2))
  1614. coords = ["{},{}".format(p.x, p.y) for p in verts]
  1615. path = "M {} L {}".format(coords[0], " L ".join(coords[1:]))
  1616. return (
  1617. '<path fill-rule="evenodd" fill="{2}" stroke="#555555" '
  1618. 'stroke-width="{0}" opacity="0.6" d="{1}" '
  1619. 'marker-start="url(#markerReverseArrow)" marker-end="url(#markerArrow)"/>'
  1620. ).format(2.*scale_factor, path, fill_color)
  1621. @property
  1622. def coefficients(self):
  1623. """The coefficients (`a`, `b`, `c`) for `ax + by + c = 0`.
  1624. See Also
  1625. ========
  1626. sympy.geometry.line.Line2D.equation
  1627. Examples
  1628. ========
  1629. >>> from sympy import Point, Line
  1630. >>> from sympy.abc import x, y
  1631. >>> p1, p2 = Point(0, 0), Point(5, 3)
  1632. >>> l = Line(p1, p2)
  1633. >>> l.coefficients
  1634. (-3, 5, 0)
  1635. >>> p3 = Point(x, y)
  1636. >>> l2 = Line(p1, p3)
  1637. >>> l2.coefficients
  1638. (-y, x, 0)
  1639. """
  1640. p1, p2 = self.points
  1641. if p1.x == p2.x:
  1642. return (S.One, S.Zero, -p1.x)
  1643. elif p1.y == p2.y:
  1644. return (S.Zero, S.One, -p1.y)
  1645. return tuple([simplify(i) for i in
  1646. (self.p1.y - self.p2.y,
  1647. self.p2.x - self.p1.x,
  1648. self.p1.x*self.p2.y - self.p1.y*self.p2.x)])
  1649. def equation(self, x='x', y='y'):
  1650. """The equation of the line: ax + by + c.
  1651. Parameters
  1652. ==========
  1653. x : str, optional
  1654. The name to use for the x-axis, default value is 'x'.
  1655. y : str, optional
  1656. The name to use for the y-axis, default value is 'y'.
  1657. Returns
  1658. =======
  1659. equation : SymPy expression
  1660. See Also
  1661. ========
  1662. sympy.geometry.line.Line2D.coefficients
  1663. Examples
  1664. ========
  1665. >>> from sympy import Point, Line
  1666. >>> p1, p2 = Point(1, 0), Point(5, 3)
  1667. >>> l1 = Line(p1, p2)
  1668. >>> l1.equation()
  1669. -3*x + 4*y + 3
  1670. """
  1671. x = _symbol(x, real=True)
  1672. y = _symbol(y, real=True)
  1673. p1, p2 = self.points
  1674. if p1.x == p2.x:
  1675. return x - p1.x
  1676. elif p1.y == p2.y:
  1677. return y - p1.y
  1678. a, b, c = self.coefficients
  1679. return a*x + b*y + c
  1680. class Ray2D(LinearEntity2D, Ray):
  1681. """
  1682. A Ray is a semi-line in the space with a source point and a direction.
  1683. Parameters
  1684. ==========
  1685. p1 : Point
  1686. The source of the Ray
  1687. p2 : Point or radian value
  1688. This point determines the direction in which the Ray propagates.
  1689. If given as an angle it is interpreted in radians with the positive
  1690. direction being ccw.
  1691. Attributes
  1692. ==========
  1693. source
  1694. xdirection
  1695. ydirection
  1696. See Also
  1697. ========
  1698. sympy.geometry.point.Point, Line
  1699. Examples
  1700. ========
  1701. >>> from sympy import Point, pi, Ray
  1702. >>> r = Ray(Point(2, 3), Point(3, 5))
  1703. >>> r
  1704. Ray2D(Point2D(2, 3), Point2D(3, 5))
  1705. >>> r.points
  1706. (Point2D(2, 3), Point2D(3, 5))
  1707. >>> r.source
  1708. Point2D(2, 3)
  1709. >>> r.xdirection
  1710. oo
  1711. >>> r.ydirection
  1712. oo
  1713. >>> r.slope
  1714. 2
  1715. >>> Ray(Point(0, 0), angle=pi/4).slope
  1716. 1
  1717. """
  1718. def __new__(cls, p1, pt=None, angle=None, **kwargs):
  1719. p1 = Point(p1, dim=2)
  1720. if pt is not None and angle is None:
  1721. try:
  1722. p2 = Point(pt, dim=2)
  1723. except (NotImplementedError, TypeError, ValueError):
  1724. raise ValueError(filldedent('''
  1725. The 2nd argument was not a valid Point; if
  1726. it was meant to be an angle it should be
  1727. given with keyword "angle".'''))
  1728. if p1 == p2:
  1729. raise ValueError('A Ray requires two distinct points.')
  1730. elif angle is not None and pt is None:
  1731. # we need to know if the angle is an odd multiple of pi/2
  1732. c = pi_coeff(sympify(angle))
  1733. p2 = None
  1734. if c is not None:
  1735. if c.is_Rational:
  1736. if c.q == 2:
  1737. if c.p == 1:
  1738. p2 = p1 + Point(0, 1)
  1739. elif c.p == 3:
  1740. p2 = p1 + Point(0, -1)
  1741. elif c.q == 1:
  1742. if c.p == 0:
  1743. p2 = p1 + Point(1, 0)
  1744. elif c.p == 1:
  1745. p2 = p1 + Point(-1, 0)
  1746. if p2 is None:
  1747. c *= S.Pi
  1748. else:
  1749. c = angle % (2*S.Pi)
  1750. if not p2:
  1751. m = 2*c/S.Pi
  1752. left = And(1 < m, m < 3) # is it in quadrant 2 or 3?
  1753. x = Piecewise((-1, left), (Piecewise((0, Eq(m % 1, 0)), (1, True)), True))
  1754. y = Piecewise((-tan(c), left), (Piecewise((1, Eq(m, 1)), (-1, Eq(m, 3)), (tan(c), True)), True))
  1755. p2 = p1 + Point(x, y)
  1756. else:
  1757. raise ValueError('A 2nd point or keyword "angle" must be used.')
  1758. return LinearEntity2D.__new__(cls, p1, p2, **kwargs)
  1759. @property
  1760. def xdirection(self):
  1761. """The x direction of the ray.
  1762. Positive infinity if the ray points in the positive x direction,
  1763. negative infinity if the ray points in the negative x direction,
  1764. or 0 if the ray is vertical.
  1765. See Also
  1766. ========
  1767. ydirection
  1768. Examples
  1769. ========
  1770. >>> from sympy import Point, Ray
  1771. >>> p1, p2, p3 = Point(0, 0), Point(1, 1), Point(0, -1)
  1772. >>> r1, r2 = Ray(p1, p2), Ray(p1, p3)
  1773. >>> r1.xdirection
  1774. oo
  1775. >>> r2.xdirection
  1776. 0
  1777. """
  1778. if self.p1.x < self.p2.x:
  1779. return S.Infinity
  1780. elif self.p1.x == self.p2.x:
  1781. return S.Zero
  1782. else:
  1783. return S.NegativeInfinity
  1784. @property
  1785. def ydirection(self):
  1786. """The y direction of the ray.
  1787. Positive infinity if the ray points in the positive y direction,
  1788. negative infinity if the ray points in the negative y direction,
  1789. or 0 if the ray is horizontal.
  1790. See Also
  1791. ========
  1792. xdirection
  1793. Examples
  1794. ========
  1795. >>> from sympy import Point, Ray
  1796. >>> p1, p2, p3 = Point(0, 0), Point(-1, -1), Point(-1, 0)
  1797. >>> r1, r2 = Ray(p1, p2), Ray(p1, p3)
  1798. >>> r1.ydirection
  1799. -oo
  1800. >>> r2.ydirection
  1801. 0
  1802. """
  1803. if self.p1.y < self.p2.y:
  1804. return S.Infinity
  1805. elif self.p1.y == self.p2.y:
  1806. return S.Zero
  1807. else:
  1808. return S.NegativeInfinity
  1809. def closing_angle(r1, r2):
  1810. """Return the angle by which r2 must be rotated so it faces the same
  1811. direction as r1.
  1812. Parameters
  1813. ==========
  1814. r1 : Ray2D
  1815. r2 : Ray2D
  1816. Returns
  1817. =======
  1818. angle : angle in radians (ccw angle is positive)
  1819. See Also
  1820. ========
  1821. LinearEntity.angle_between
  1822. Examples
  1823. ========
  1824. >>> from sympy import Ray, pi
  1825. >>> r1 = Ray((0, 0), (1, 0))
  1826. >>> r2 = r1.rotate(-pi/2)
  1827. >>> angle = r1.closing_angle(r2); angle
  1828. pi/2
  1829. >>> r2.rotate(angle).direction.unit == r1.direction.unit
  1830. True
  1831. >>> r2.closing_angle(r1)
  1832. -pi/2
  1833. """
  1834. if not all(isinstance(r, Ray2D) for r in (r1, r2)):
  1835. # although the direction property is defined for
  1836. # all linear entities, only the Ray is truly a
  1837. # directed object
  1838. raise TypeError('Both arguments must be Ray2D objects.')
  1839. a1 = atan2(*list(reversed(r1.direction.args)))
  1840. a2 = atan2(*list(reversed(r2.direction.args)))
  1841. if a1*a2 < 0:
  1842. a1 = 2*S.Pi + a1 if a1 < 0 else a1
  1843. a2 = 2*S.Pi + a2 if a2 < 0 else a2
  1844. return a1 - a2
  1845. class Segment2D(LinearEntity2D, Segment):
  1846. """A line segment in 2D space.
  1847. Parameters
  1848. ==========
  1849. p1 : Point
  1850. p2 : Point
  1851. Attributes
  1852. ==========
  1853. length : number or SymPy expression
  1854. midpoint : Point
  1855. See Also
  1856. ========
  1857. sympy.geometry.point.Point, Line
  1858. Examples
  1859. ========
  1860. >>> from sympy import Point, Segment
  1861. >>> Segment((1, 0), (1, 1)) # tuples are interpreted as pts
  1862. Segment2D(Point2D(1, 0), Point2D(1, 1))
  1863. >>> s = Segment(Point(4, 3), Point(1, 1)); s
  1864. Segment2D(Point2D(4, 3), Point2D(1, 1))
  1865. >>> s.points
  1866. (Point2D(4, 3), Point2D(1, 1))
  1867. >>> s.slope
  1868. 2/3
  1869. >>> s.length
  1870. sqrt(13)
  1871. >>> s.midpoint
  1872. Point2D(5/2, 2)
  1873. """
  1874. def __new__(cls, p1, p2, **kwargs):
  1875. p1 = Point(p1, dim=2)
  1876. p2 = Point(p2, dim=2)
  1877. if p1 == p2:
  1878. return p1
  1879. return LinearEntity2D.__new__(cls, p1, p2, **kwargs)
  1880. def _svg(self, scale_factor=1., fill_color="#66cc99"):
  1881. """Returns SVG path element for the LinearEntity.
  1882. Parameters
  1883. ==========
  1884. scale_factor : float
  1885. Multiplication factor for the SVG stroke-width. Default is 1.
  1886. fill_color : str, optional
  1887. Hex string for fill color. Default is "#66cc99".
  1888. """
  1889. verts = (N(self.p1), N(self.p2))
  1890. coords = ["{},{}".format(p.x, p.y) for p in verts]
  1891. path = "M {} L {}".format(coords[0], " L ".join(coords[1:]))
  1892. return (
  1893. '<path fill-rule="evenodd" fill="{2}" stroke="#555555" '
  1894. 'stroke-width="{0}" opacity="0.6" d="{1}" />'
  1895. ).format(2.*scale_factor, path, fill_color)
  1896. class LinearEntity3D(LinearEntity):
  1897. """An base class for all linear entities (line, ray and segment)
  1898. in a 3-dimensional Euclidean space.
  1899. Attributes
  1900. ==========
  1901. p1
  1902. p2
  1903. direction_ratio
  1904. direction_cosine
  1905. points
  1906. Notes
  1907. =====
  1908. This is a base class and is not meant to be instantiated.
  1909. """
  1910. def __new__(cls, p1, p2, **kwargs):
  1911. p1 = Point3D(p1, dim=3)
  1912. p2 = Point3D(p2, dim=3)
  1913. if p1 == p2:
  1914. # if it makes sense to return a Point, handle in subclass
  1915. raise ValueError(
  1916. "%s.__new__ requires two unique Points." % cls.__name__)
  1917. return GeometryEntity.__new__(cls, p1, p2, **kwargs)
  1918. ambient_dimension = 3
  1919. @property
  1920. def direction_ratio(self):
  1921. """The direction ratio of a given line in 3D.
  1922. See Also
  1923. ========
  1924. sympy.geometry.line.Line3D.equation
  1925. Examples
  1926. ========
  1927. >>> from sympy import Point3D, Line3D
  1928. >>> p1, p2 = Point3D(0, 0, 0), Point3D(5, 3, 1)
  1929. >>> l = Line3D(p1, p2)
  1930. >>> l.direction_ratio
  1931. [5, 3, 1]
  1932. """
  1933. p1, p2 = self.points
  1934. return p1.direction_ratio(p2)
  1935. @property
  1936. def direction_cosine(self):
  1937. """The normalized direction ratio of a given line in 3D.
  1938. See Also
  1939. ========
  1940. sympy.geometry.line.Line3D.equation
  1941. Examples
  1942. ========
  1943. >>> from sympy import Point3D, Line3D
  1944. >>> p1, p2 = Point3D(0, 0, 0), Point3D(5, 3, 1)
  1945. >>> l = Line3D(p1, p2)
  1946. >>> l.direction_cosine
  1947. [sqrt(35)/7, 3*sqrt(35)/35, sqrt(35)/35]
  1948. >>> sum(i**2 for i in _)
  1949. 1
  1950. """
  1951. p1, p2 = self.points
  1952. return p1.direction_cosine(p2)
  1953. class Line3D(LinearEntity3D, Line):
  1954. """An infinite 3D line in space.
  1955. A line is declared with two distinct points or a point and direction_ratio
  1956. as defined using keyword `direction_ratio`.
  1957. Parameters
  1958. ==========
  1959. p1 : Point3D
  1960. pt : Point3D
  1961. direction_ratio : list
  1962. See Also
  1963. ========
  1964. sympy.geometry.point.Point3D
  1965. sympy.geometry.line.Line
  1966. sympy.geometry.line.Line2D
  1967. Examples
  1968. ========
  1969. >>> from sympy import Line3D, Point3D
  1970. >>> L = Line3D(Point3D(2, 3, 4), Point3D(3, 5, 1))
  1971. >>> L
  1972. Line3D(Point3D(2, 3, 4), Point3D(3, 5, 1))
  1973. >>> L.points
  1974. (Point3D(2, 3, 4), Point3D(3, 5, 1))
  1975. """
  1976. def __new__(cls, p1, pt=None, direction_ratio=(), **kwargs):
  1977. if isinstance(p1, LinearEntity3D):
  1978. if pt is not None:
  1979. raise ValueError('if p1 is a LinearEntity, pt must be None.')
  1980. p1, pt = p1.args
  1981. else:
  1982. p1 = Point(p1, dim=3)
  1983. if pt is not None and len(direction_ratio) == 0:
  1984. pt = Point(pt, dim=3)
  1985. elif len(direction_ratio) == 3 and pt is None:
  1986. pt = Point3D(p1.x + direction_ratio[0], p1.y + direction_ratio[1],
  1987. p1.z + direction_ratio[2])
  1988. else:
  1989. raise ValueError('A 2nd Point or keyword "direction_ratio" must '
  1990. 'be used.')
  1991. return LinearEntity3D.__new__(cls, p1, pt, **kwargs)
  1992. def equation(self, x='x', y='y', z='z', k=None):
  1993. """Return the equations that define the line in 3D.
  1994. Parameters
  1995. ==========
  1996. x : str, optional
  1997. The name to use for the x-axis, default value is 'x'.
  1998. y : str, optional
  1999. The name to use for the y-axis, default value is 'y'.
  2000. z : str, optional
  2001. The name to use for the z-axis, default value is 'z'.
  2002. k : str, optional
  2003. .. deprecated:: 1.2
  2004. The ``k`` flag is deprecated. It does nothing.
  2005. Returns
  2006. =======
  2007. equation : Tuple of simultaneous equations
  2008. Examples
  2009. ========
  2010. >>> from sympy import Point3D, Line3D, solve
  2011. >>> from sympy.abc import x, y, z
  2012. >>> p1, p2 = Point3D(1, 0, 0), Point3D(5, 3, 0)
  2013. >>> l1 = Line3D(p1, p2)
  2014. >>> eq = l1.equation(x, y, z); eq
  2015. (-3*x + 4*y + 3, z)
  2016. >>> solve(eq.subs(z, 0), (x, y, z))
  2017. {x: 4*y/3 + 1}
  2018. """
  2019. if k is not None:
  2020. sympy_deprecation_warning(
  2021. """
  2022. The 'k' argument to Line3D.equation() is deprecated. Is
  2023. currently has no effect, so it may be omitted.
  2024. """,
  2025. deprecated_since_version="1.2",
  2026. active_deprecations_target='deprecated-line3d-equation-k',
  2027. )
  2028. from sympy.solvers.solvers import solve
  2029. x, y, z, k = [_symbol(i, real=True) for i in (x, y, z, 'k')]
  2030. p1, p2 = self.points
  2031. d1, d2, d3 = p1.direction_ratio(p2)
  2032. x1, y1, z1 = p1
  2033. eqs = [-d1*k + x - x1, -d2*k + y - y1, -d3*k + z - z1]
  2034. # eliminate k from equations by solving first eq with k for k
  2035. for i, e in enumerate(eqs):
  2036. if e.has(k):
  2037. kk = solve(eqs[i], k)[0]
  2038. eqs.pop(i)
  2039. break
  2040. return Tuple(*[i.subs(k, kk).as_numer_denom()[0] for i in eqs])
  2041. class Ray3D(LinearEntity3D, Ray):
  2042. """
  2043. A Ray is a semi-line in the space with a source point and a direction.
  2044. Parameters
  2045. ==========
  2046. p1 : Point3D
  2047. The source of the Ray
  2048. p2 : Point or a direction vector
  2049. direction_ratio: Determines the direction in which the Ray propagates.
  2050. Attributes
  2051. ==========
  2052. source
  2053. xdirection
  2054. ydirection
  2055. zdirection
  2056. See Also
  2057. ========
  2058. sympy.geometry.point.Point3D, Line3D
  2059. Examples
  2060. ========
  2061. >>> from sympy import Point3D, Ray3D
  2062. >>> r = Ray3D(Point3D(2, 3, 4), Point3D(3, 5, 0))
  2063. >>> r
  2064. Ray3D(Point3D(2, 3, 4), Point3D(3, 5, 0))
  2065. >>> r.points
  2066. (Point3D(2, 3, 4), Point3D(3, 5, 0))
  2067. >>> r.source
  2068. Point3D(2, 3, 4)
  2069. >>> r.xdirection
  2070. oo
  2071. >>> r.ydirection
  2072. oo
  2073. >>> r.direction_ratio
  2074. [1, 2, -4]
  2075. """
  2076. def __new__(cls, p1, pt=None, direction_ratio=(), **kwargs):
  2077. if isinstance(p1, LinearEntity3D):
  2078. if pt is not None:
  2079. raise ValueError('If p1 is a LinearEntity, pt must be None')
  2080. p1, pt = p1.args
  2081. else:
  2082. p1 = Point(p1, dim=3)
  2083. if pt is not None and len(direction_ratio) == 0:
  2084. pt = Point(pt, dim=3)
  2085. elif len(direction_ratio) == 3 and pt is None:
  2086. pt = Point3D(p1.x + direction_ratio[0], p1.y + direction_ratio[1],
  2087. p1.z + direction_ratio[2])
  2088. else:
  2089. raise ValueError(filldedent('''
  2090. A 2nd Point or keyword "direction_ratio" must be used.
  2091. '''))
  2092. return LinearEntity3D.__new__(cls, p1, pt, **kwargs)
  2093. @property
  2094. def xdirection(self):
  2095. """The x direction of the ray.
  2096. Positive infinity if the ray points in the positive x direction,
  2097. negative infinity if the ray points in the negative x direction,
  2098. or 0 if the ray is vertical.
  2099. See Also
  2100. ========
  2101. ydirection
  2102. Examples
  2103. ========
  2104. >>> from sympy import Point3D, Ray3D
  2105. >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(1, 1, 1), Point3D(0, -1, 0)
  2106. >>> r1, r2 = Ray3D(p1, p2), Ray3D(p1, p3)
  2107. >>> r1.xdirection
  2108. oo
  2109. >>> r2.xdirection
  2110. 0
  2111. """
  2112. if self.p1.x < self.p2.x:
  2113. return S.Infinity
  2114. elif self.p1.x == self.p2.x:
  2115. return S.Zero
  2116. else:
  2117. return S.NegativeInfinity
  2118. @property
  2119. def ydirection(self):
  2120. """The y direction of the ray.
  2121. Positive infinity if the ray points in the positive y direction,
  2122. negative infinity if the ray points in the negative y direction,
  2123. or 0 if the ray is horizontal.
  2124. See Also
  2125. ========
  2126. xdirection
  2127. Examples
  2128. ========
  2129. >>> from sympy import Point3D, Ray3D
  2130. >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(-1, -1, -1), Point3D(-1, 0, 0)
  2131. >>> r1, r2 = Ray3D(p1, p2), Ray3D(p1, p3)
  2132. >>> r1.ydirection
  2133. -oo
  2134. >>> r2.ydirection
  2135. 0
  2136. """
  2137. if self.p1.y < self.p2.y:
  2138. return S.Infinity
  2139. elif self.p1.y == self.p2.y:
  2140. return S.Zero
  2141. else:
  2142. return S.NegativeInfinity
  2143. @property
  2144. def zdirection(self):
  2145. """The z direction of the ray.
  2146. Positive infinity if the ray points in the positive z direction,
  2147. negative infinity if the ray points in the negative z direction,
  2148. or 0 if the ray is horizontal.
  2149. See Also
  2150. ========
  2151. xdirection
  2152. Examples
  2153. ========
  2154. >>> from sympy import Point3D, Ray3D
  2155. >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(-1, -1, -1), Point3D(-1, 0, 0)
  2156. >>> r1, r2 = Ray3D(p1, p2), Ray3D(p1, p3)
  2157. >>> r1.ydirection
  2158. -oo
  2159. >>> r2.ydirection
  2160. 0
  2161. >>> r2.zdirection
  2162. 0
  2163. """
  2164. if self.p1.z < self.p2.z:
  2165. return S.Infinity
  2166. elif self.p1.z == self.p2.z:
  2167. return S.Zero
  2168. else:
  2169. return S.NegativeInfinity
  2170. class Segment3D(LinearEntity3D, Segment):
  2171. """A line segment in a 3D space.
  2172. Parameters
  2173. ==========
  2174. p1 : Point3D
  2175. p2 : Point3D
  2176. Attributes
  2177. ==========
  2178. length : number or SymPy expression
  2179. midpoint : Point3D
  2180. See Also
  2181. ========
  2182. sympy.geometry.point.Point3D, Line3D
  2183. Examples
  2184. ========
  2185. >>> from sympy import Point3D, Segment3D
  2186. >>> Segment3D((1, 0, 0), (1, 1, 1)) # tuples are interpreted as pts
  2187. Segment3D(Point3D(1, 0, 0), Point3D(1, 1, 1))
  2188. >>> s = Segment3D(Point3D(4, 3, 9), Point3D(1, 1, 7)); s
  2189. Segment3D(Point3D(4, 3, 9), Point3D(1, 1, 7))
  2190. >>> s.points
  2191. (Point3D(4, 3, 9), Point3D(1, 1, 7))
  2192. >>> s.length
  2193. sqrt(17)
  2194. >>> s.midpoint
  2195. Point3D(5/2, 2, 8)
  2196. """
  2197. def __new__(cls, p1, p2, **kwargs):
  2198. p1 = Point(p1, dim=3)
  2199. p2 = Point(p2, dim=3)
  2200. if p1 == p2:
  2201. return p1
  2202. return LinearEntity3D.__new__(cls, p1, p2, **kwargs)