line.py 77 KB

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