1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785 |
- """Line-like geometrical entities.
- Contains
- ========
- LinearEntity
- Line
- Ray
- Segment
- LinearEntity2D
- Line2D
- Ray2D
- Segment2D
- LinearEntity3D
- Line3D
- Ray3D
- Segment3D
- """
- from sympy.core.containers import Tuple
- from sympy.core.evalf import N
- from sympy.core.expr import Expr
- from sympy.core.numbers import Rational, oo
- from sympy.core.relational import Eq
- from sympy.core.singleton import S
- from sympy.core.sorting import ordered
- from sympy.core.symbol import _symbol, Dummy, uniquely_named_symbol
- from sympy.core.sympify import sympify
- from sympy.functions.elementary.piecewise import Piecewise
- from sympy.functions.elementary.trigonometric import (_pi_coeff as pi_coeff, acos, tan, atan2)
- from .entity import GeometryEntity, GeometrySet
- from .exceptions import GeometryError
- from .point import Point, Point3D
- from .util import find, intersection
- from sympy.logic.boolalg import And
- from sympy.matrices import Matrix
- from sympy.sets.sets import Intersection
- from sympy.simplify.simplify import simplify
- from sympy.solvers.solveset import linear_coeffs
- from sympy.utilities.exceptions import sympy_deprecation_warning
- from sympy.utilities.misc import Undecidable, filldedent
- import random
- class LinearEntity(GeometrySet):
- """A base class for all linear entities (Line, Ray and Segment)
- in n-dimensional Euclidean space.
- Attributes
- ==========
- ambient_dimension
- direction
- length
- p1
- p2
- points
- Notes
- =====
- This is an abstract class and is not meant to be instantiated.
- See Also
- ========
- sympy.geometry.entity.GeometryEntity
- """
- def __new__(cls, p1, p2=None, **kwargs):
- p1, p2 = Point._normalize_dimension(p1, p2)
- if p1 == p2:
- # sometimes we return a single point if we are not given two unique
- # points. This is done in the specific subclass
- raise ValueError(
- "%s.__new__ requires two unique Points." % cls.__name__)
- if len(p1) != len(p2):
- raise ValueError(
- "%s.__new__ requires two Points of equal dimension." % cls.__name__)
- return GeometryEntity.__new__(cls, p1, p2, **kwargs)
- def __contains__(self, other):
- """Return a definitive answer or else raise an error if it cannot
- be determined that other is on the boundaries of self."""
- result = self.contains(other)
- if result is not None:
- return result
- else:
- raise Undecidable(
- "Cannot decide whether '%s' contains '%s'" % (self, other))
- def _span_test(self, other):
- """Test whether the point `other` lies in the positive span of `self`.
- A point x is 'in front' of a point y if x.dot(y) >= 0. Return
- -1 if `other` is behind `self.p1`, 0 if `other` is `self.p1` and
- and 1 if `other` is in front of `self.p1`."""
- if self.p1 == other:
- return 0
- rel_pos = other - self.p1
- d = self.direction
- if d.dot(rel_pos) > 0:
- return 1
- return -1
- @property
- def ambient_dimension(self):
- """A property method that returns the dimension of LinearEntity
- object.
- Parameters
- ==========
- p1 : LinearEntity
- Returns
- =======
- dimension : integer
- Examples
- ========
- >>> from sympy import Point, Line
- >>> p1, p2 = Point(0, 0), Point(1, 1)
- >>> l1 = Line(p1, p2)
- >>> l1.ambient_dimension
- 2
- >>> from sympy import Point, Line
- >>> p1, p2 = Point(0, 0, 0), Point(1, 1, 1)
- >>> l1 = Line(p1, p2)
- >>> l1.ambient_dimension
- 3
- """
- return len(self.p1)
- def angle_between(l1, l2):
- """Return the non-reflex angle formed by rays emanating from
- the origin with directions the same as the direction vectors
- of the linear entities.
- Parameters
- ==========
- l1 : LinearEntity
- l2 : LinearEntity
- Returns
- =======
- angle : angle in radians
- Notes
- =====
- From the dot product of vectors v1 and v2 it is known that:
- ``dot(v1, v2) = |v1|*|v2|*cos(A)``
- where A is the angle formed between the two vectors. We can
- get the directional vectors of the two lines and readily
- find the angle between the two using the above formula.
- See Also
- ========
- is_perpendicular, Ray2D.closing_angle
- Examples
- ========
- >>> from sympy import Line
- >>> e = Line((0, 0), (1, 0))
- >>> ne = Line((0, 0), (1, 1))
- >>> sw = Line((1, 1), (0, 0))
- >>> ne.angle_between(e)
- pi/4
- >>> sw.angle_between(e)
- 3*pi/4
- To obtain the non-obtuse angle at the intersection of lines, use
- the ``smallest_angle_between`` method:
- >>> sw.smallest_angle_between(e)
- pi/4
- >>> from sympy import Point3D, Line3D
- >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(1, 1, 1), Point3D(-1, 2, 0)
- >>> l1, l2 = Line3D(p1, p2), Line3D(p2, p3)
- >>> l1.angle_between(l2)
- acos(-sqrt(2)/3)
- >>> l1.smallest_angle_between(l2)
- acos(sqrt(2)/3)
- """
- if not isinstance(l1, LinearEntity) and not isinstance(l2, LinearEntity):
- raise TypeError('Must pass only LinearEntity objects')
- v1, v2 = l1.direction, l2.direction
- return acos(v1.dot(v2)/(abs(v1)*abs(v2)))
- def smallest_angle_between(l1, l2):
- """Return the smallest angle formed at the intersection of the
- lines containing the linear entities.
- Parameters
- ==========
- l1 : LinearEntity
- l2 : LinearEntity
- Returns
- =======
- angle : angle in radians
- See Also
- ========
- angle_between, is_perpendicular, Ray2D.closing_angle
- Examples
- ========
- >>> from sympy import Point, Line
- >>> p1, p2, p3 = Point(0, 0), Point(0, 4), Point(2, -2)
- >>> l1, l2 = Line(p1, p2), Line(p1, p3)
- >>> l1.smallest_angle_between(l2)
- pi/4
- See Also
- ========
- angle_between, Ray2D.closing_angle
- """
- if not isinstance(l1, LinearEntity) and not isinstance(l2, LinearEntity):
- raise TypeError('Must pass only LinearEntity objects')
- v1, v2 = l1.direction, l2.direction
- return acos(abs(v1.dot(v2))/(abs(v1)*abs(v2)))
- def arbitrary_point(self, parameter='t'):
- """A parameterized point on the Line.
- Parameters
- ==========
- parameter : str, optional
- The name of the parameter which will be used for the parametric
- point. The default value is 't'. When this parameter is 0, the
- first point used to define the line will be returned, and when
- it is 1 the second point will be returned.
- Returns
- =======
- point : Point
- Raises
- ======
- ValueError
- When ``parameter`` already appears in the Line's definition.
- See Also
- ========
- sympy.geometry.point.Point
- Examples
- ========
- >>> from sympy import Point, Line
- >>> p1, p2 = Point(1, 0), Point(5, 3)
- >>> l1 = Line(p1, p2)
- >>> l1.arbitrary_point()
- Point2D(4*t + 1, 3*t)
- >>> from sympy import Point3D, Line3D
- >>> p1, p2 = Point3D(1, 0, 0), Point3D(5, 3, 1)
- >>> l1 = Line3D(p1, p2)
- >>> l1.arbitrary_point()
- Point3D(4*t + 1, 3*t, t)
- """
- t = _symbol(parameter, real=True)
- if t.name in (f.name for f in self.free_symbols):
- raise ValueError(filldedent('''
- Symbol %s already appears in object
- and cannot be used as a parameter.
- ''' % t.name))
- # multiply on the right so the variable gets
- # combined with the coordinates of the point
- return self.p1 + (self.p2 - self.p1)*t
- @staticmethod
- def are_concurrent(*lines):
- """Is a sequence of linear entities concurrent?
- Two or more linear entities are concurrent if they all
- intersect at a single point.
- Parameters
- ==========
- lines : a sequence of linear entities.
- Returns
- =======
- True : if the set of linear entities intersect in one point
- False : otherwise.
- See Also
- ========
- sympy.geometry.util.intersection
- Examples
- ========
- >>> from sympy import Point, Line
- >>> p1, p2 = Point(0, 0), Point(3, 5)
- >>> p3, p4 = Point(-2, -2), Point(0, 2)
- >>> l1, l2, l3 = Line(p1, p2), Line(p1, p3), Line(p1, p4)
- >>> Line.are_concurrent(l1, l2, l3)
- True
- >>> l4 = Line(p2, p3)
- >>> Line.are_concurrent(l2, l3, l4)
- False
- >>> from sympy import Point3D, Line3D
- >>> p1, p2 = Point3D(0, 0, 0), Point3D(3, 5, 2)
- >>> p3, p4 = Point3D(-2, -2, -2), Point3D(0, 2, 1)
- >>> l1, l2, l3 = Line3D(p1, p2), Line3D(p1, p3), Line3D(p1, p4)
- >>> Line3D.are_concurrent(l1, l2, l3)
- True
- >>> l4 = Line3D(p2, p3)
- >>> Line3D.are_concurrent(l2, l3, l4)
- False
- """
- common_points = Intersection(*lines)
- if common_points.is_FiniteSet and len(common_points) == 1:
- return True
- return False
- def contains(self, other):
- """Subclasses should implement this method and should return
- True if other is on the boundaries of self;
- False if not on the boundaries of self;
- None if a determination cannot be made."""
- raise NotImplementedError()
- @property
- def direction(self):
- """The direction vector of the LinearEntity.
- Returns
- =======
- p : a Point; the ray from the origin to this point is the
- direction of `self`
- Examples
- ========
- >>> from sympy import Line
- >>> a, b = (1, 1), (1, 3)
- >>> Line(a, b).direction
- Point2D(0, 2)
- >>> Line(b, a).direction
- Point2D(0, -2)
- This can be reported so the distance from the origin is 1:
- >>> Line(b, a).direction.unit
- Point2D(0, -1)
- See Also
- ========
- sympy.geometry.point.Point.unit
- """
- return self.p2 - self.p1
- def intersection(self, other):
- """The intersection with another geometrical entity.
- Parameters
- ==========
- o : Point or LinearEntity
- Returns
- =======
- intersection : list of geometrical entities
- See Also
- ========
- sympy.geometry.point.Point
- Examples
- ========
- >>> from sympy import Point, Line, Segment
- >>> p1, p2, p3 = Point(0, 0), Point(1, 1), Point(7, 7)
- >>> l1 = Line(p1, p2)
- >>> l1.intersection(p3)
- [Point2D(7, 7)]
- >>> p4, p5 = Point(5, 0), Point(0, 3)
- >>> l2 = Line(p4, p5)
- >>> l1.intersection(l2)
- [Point2D(15/8, 15/8)]
- >>> p6, p7 = Point(0, 5), Point(2, 6)
- >>> s1 = Segment(p6, p7)
- >>> l1.intersection(s1)
- []
- >>> from sympy import Point3D, Line3D, Segment3D
- >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(1, 1, 1), Point3D(7, 7, 7)
- >>> l1 = Line3D(p1, p2)
- >>> l1.intersection(p3)
- [Point3D(7, 7, 7)]
- >>> l1 = Line3D(Point3D(4,19,12), Point3D(5,25,17))
- >>> l2 = Line3D(Point3D(-3, -15, -19), direction_ratio=[2,8,8])
- >>> l1.intersection(l2)
- [Point3D(1, 1, -3)]
- >>> p6, p7 = Point3D(0, 5, 2), Point3D(2, 6, 3)
- >>> s1 = Segment3D(p6, p7)
- >>> l1.intersection(s1)
- []
- """
- def intersect_parallel_rays(ray1, ray2):
- if ray1.direction.dot(ray2.direction) > 0:
- # rays point in the same direction
- # so return the one that is "in front"
- return [ray2] if ray1._span_test(ray2.p1) >= 0 else [ray1]
- else:
- # rays point in opposite directions
- st = ray1._span_test(ray2.p1)
- if st < 0:
- return []
- elif st == 0:
- return [ray2.p1]
- return [Segment(ray1.p1, ray2.p1)]
- def intersect_parallel_ray_and_segment(ray, seg):
- st1, st2 = ray._span_test(seg.p1), ray._span_test(seg.p2)
- if st1 < 0 and st2 < 0:
- return []
- elif st1 >= 0 and st2 >= 0:
- return [seg]
- elif st1 >= 0: # st2 < 0:
- return [Segment(ray.p1, seg.p1)]
- else: # st1 < 0 and st2 >= 0:
- return [Segment(ray.p1, seg.p2)]
- def intersect_parallel_segments(seg1, seg2):
- if seg1.contains(seg2):
- return [seg2]
- if seg2.contains(seg1):
- return [seg1]
- # direct the segments so they're oriented the same way
- if seg1.direction.dot(seg2.direction) < 0:
- seg2 = Segment(seg2.p2, seg2.p1)
- # order the segments so seg1 is "behind" seg2
- if seg1._span_test(seg2.p1) < 0:
- seg1, seg2 = seg2, seg1
- if seg2._span_test(seg1.p2) < 0:
- return []
- return [Segment(seg2.p1, seg1.p2)]
- if not isinstance(other, GeometryEntity):
- other = Point(other, dim=self.ambient_dimension)
- if other.is_Point:
- if self.contains(other):
- return [other]
- else:
- return []
- elif isinstance(other, LinearEntity):
- # break into cases based on whether
- # the lines are parallel, non-parallel intersecting, or skew
- pts = Point._normalize_dimension(self.p1, self.p2, other.p1, other.p2)
- rank = Point.affine_rank(*pts)
- if rank == 1:
- # we're collinear
- if isinstance(self, Line):
- return [other]
- if isinstance(other, Line):
- return [self]
- if isinstance(self, Ray) and isinstance(other, Ray):
- return intersect_parallel_rays(self, other)
- if isinstance(self, Ray) and isinstance(other, Segment):
- return intersect_parallel_ray_and_segment(self, other)
- if isinstance(self, Segment) and isinstance(other, Ray):
- return intersect_parallel_ray_and_segment(other, self)
- if isinstance(self, Segment) and isinstance(other, Segment):
- return intersect_parallel_segments(self, other)
- elif rank == 2:
- # we're in the same plane
- l1 = Line(*pts[:2])
- l2 = Line(*pts[2:])
- # check to see if we're parallel. If we are, we can't
- # be intersecting, since the collinear case was already
- # handled
- if l1.direction.is_scalar_multiple(l2.direction):
- return []
- # find the intersection as if everything were lines
- # by solving the equation t*d + p1 == s*d' + p1'
- m = Matrix([l1.direction, -l2.direction]).transpose()
- v = Matrix([l2.p1 - l1.p1]).transpose()
- # we cannot use m.solve(v) because that only works for square matrices
- m_rref, pivots = m.col_insert(2, v).rref(simplify=True)
- # rank == 2 ensures we have 2 pivots, but let's check anyway
- if len(pivots) != 2:
- raise GeometryError("Failed when solving Mx=b when M={} and b={}".format(m, v))
- coeff = m_rref[0, 2]
- line_intersection = l1.direction*coeff + self.p1
- # if we're both lines, we can skip a containment check
- if isinstance(self, Line) and isinstance(other, Line):
- return [line_intersection]
- if ((isinstance(self, Line) or
- self.contains(line_intersection)) and
- other.contains(line_intersection)):
- return [line_intersection]
- return []
- else:
- # we're skew
- return []
- return other.intersection(self)
- def is_parallel(l1, l2):
- """Are two linear entities parallel?
- Parameters
- ==========
- l1 : LinearEntity
- l2 : LinearEntity
- Returns
- =======
- True : if l1 and l2 are parallel,
- False : otherwise.
- See Also
- ========
- coefficients
- Examples
- ========
- >>> from sympy import Point, Line
- >>> p1, p2 = Point(0, 0), Point(1, 1)
- >>> p3, p4 = Point(3, 4), Point(6, 7)
- >>> l1, l2 = Line(p1, p2), Line(p3, p4)
- >>> Line.is_parallel(l1, l2)
- True
- >>> p5 = Point(6, 6)
- >>> l3 = Line(p3, p5)
- >>> Line.is_parallel(l1, l3)
- False
- >>> from sympy import Point3D, Line3D
- >>> p1, p2 = Point3D(0, 0, 0), Point3D(3, 4, 5)
- >>> p3, p4 = Point3D(2, 1, 1), Point3D(8, 9, 11)
- >>> l1, l2 = Line3D(p1, p2), Line3D(p3, p4)
- >>> Line3D.is_parallel(l1, l2)
- True
- >>> p5 = Point3D(6, 6, 6)
- >>> l3 = Line3D(p3, p5)
- >>> Line3D.is_parallel(l1, l3)
- False
- """
- if not isinstance(l1, LinearEntity) and not isinstance(l2, LinearEntity):
- raise TypeError('Must pass only LinearEntity objects')
- return l1.direction.is_scalar_multiple(l2.direction)
- def is_perpendicular(l1, l2):
- """Are two linear entities perpendicular?
- Parameters
- ==========
- l1 : LinearEntity
- l2 : LinearEntity
- Returns
- =======
- True : if l1 and l2 are perpendicular,
- False : otherwise.
- See Also
- ========
- coefficients
- Examples
- ========
- >>> from sympy import Point, Line
- >>> p1, p2, p3 = Point(0, 0), Point(1, 1), Point(-1, 1)
- >>> l1, l2 = Line(p1, p2), Line(p1, p3)
- >>> l1.is_perpendicular(l2)
- True
- >>> p4 = Point(5, 3)
- >>> l3 = Line(p1, p4)
- >>> l1.is_perpendicular(l3)
- False
- >>> from sympy import Point3D, Line3D
- >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(1, 1, 1), Point3D(-1, 2, 0)
- >>> l1, l2 = Line3D(p1, p2), Line3D(p2, p3)
- >>> l1.is_perpendicular(l2)
- False
- >>> p4 = Point3D(5, 3, 7)
- >>> l3 = Line3D(p1, p4)
- >>> l1.is_perpendicular(l3)
- False
- """
- if not isinstance(l1, LinearEntity) and not isinstance(l2, LinearEntity):
- raise TypeError('Must pass only LinearEntity objects')
- return S.Zero.equals(l1.direction.dot(l2.direction))
- def is_similar(self, other):
- """
- Return True if self and other are contained in the same line.
- Examples
- ========
- >>> from sympy import Point, Line
- >>> p1, p2, p3 = Point(0, 1), Point(3, 4), Point(2, 3)
- >>> l1 = Line(p1, p2)
- >>> l2 = Line(p1, p3)
- >>> l1.is_similar(l2)
- True
- """
- l = Line(self.p1, self.p2)
- return l.contains(other)
- @property
- def length(self):
- """
- The length of the line.
- Examples
- ========
- >>> from sympy import Point, Line
- >>> p1, p2 = Point(0, 0), Point(3, 5)
- >>> l1 = Line(p1, p2)
- >>> l1.length
- oo
- """
- return S.Infinity
- @property
- def p1(self):
- """The first defining point of a linear entity.
- See Also
- ========
- sympy.geometry.point.Point
- Examples
- ========
- >>> from sympy import Point, Line
- >>> p1, p2 = Point(0, 0), Point(5, 3)
- >>> l = Line(p1, p2)
- >>> l.p1
- Point2D(0, 0)
- """
- return self.args[0]
- @property
- def p2(self):
- """The second defining point of a linear entity.
- See Also
- ========
- sympy.geometry.point.Point
- Examples
- ========
- >>> from sympy import Point, Line
- >>> p1, p2 = Point(0, 0), Point(5, 3)
- >>> l = Line(p1, p2)
- >>> l.p2
- Point2D(5, 3)
- """
- return self.args[1]
- def parallel_line(self, p):
- """Create a new Line parallel to this linear entity which passes
- through the point `p`.
- Parameters
- ==========
- p : Point
- Returns
- =======
- line : Line
- See Also
- ========
- is_parallel
- Examples
- ========
- >>> from sympy import Point, Line
- >>> p1, p2, p3 = Point(0, 0), Point(2, 3), Point(-2, 2)
- >>> l1 = Line(p1, p2)
- >>> l2 = l1.parallel_line(p3)
- >>> p3 in l2
- True
- >>> l1.is_parallel(l2)
- True
- >>> from sympy import Point3D, Line3D
- >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(2, 3, 4), Point3D(-2, 2, 0)
- >>> l1 = Line3D(p1, p2)
- >>> l2 = l1.parallel_line(p3)
- >>> p3 in l2
- True
- >>> l1.is_parallel(l2)
- True
- """
- p = Point(p, dim=self.ambient_dimension)
- return Line(p, p + self.direction)
- def perpendicular_line(self, p):
- """Create a new Line perpendicular to this linear entity which passes
- through the point `p`.
- Parameters
- ==========
- p : Point
- Returns
- =======
- line : Line
- See Also
- ========
- sympy.geometry.line.LinearEntity.is_perpendicular, perpendicular_segment
- Examples
- ========
- >>> from sympy import Point, Line
- >>> p1, p2, p3 = Point(0, 0), Point(2, 3), Point(-2, 2)
- >>> l1 = Line(p1, p2)
- >>> l2 = l1.perpendicular_line(p3)
- >>> p3 in l2
- True
- >>> l1.is_perpendicular(l2)
- True
- >>> from sympy import Point3D, Line3D
- >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(2, 3, 4), Point3D(-2, 2, 0)
- >>> l1 = Line3D(p1, p2)
- >>> l2 = l1.perpendicular_line(p3)
- >>> p3 in l2
- True
- >>> l1.is_perpendicular(l2)
- True
- """
- p = Point(p, dim=self.ambient_dimension)
- if p in self:
- p = p + self.direction.orthogonal_direction
- return Line(p, self.projection(p))
- def perpendicular_segment(self, p):
- """Create a perpendicular line segment from `p` to this line.
- The enpoints of the segment are ``p`` and the closest point in
- the line containing self. (If self is not a line, the point might
- not be in self.)
- Parameters
- ==========
- p : Point
- Returns
- =======
- segment : Segment
- Notes
- =====
- Returns `p` itself if `p` is on this linear entity.
- See Also
- ========
- perpendicular_line
- Examples
- ========
- >>> from sympy import Point, Line
- >>> p1, p2, p3 = Point(0, 0), Point(1, 1), Point(0, 2)
- >>> l1 = Line(p1, p2)
- >>> s1 = l1.perpendicular_segment(p3)
- >>> l1.is_perpendicular(s1)
- True
- >>> p3 in s1
- True
- >>> l1.perpendicular_segment(Point(4, 0))
- Segment2D(Point2D(4, 0), Point2D(2, 2))
- >>> from sympy import Point3D, Line3D
- >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(1, 1, 1), Point3D(0, 2, 0)
- >>> l1 = Line3D(p1, p2)
- >>> s1 = l1.perpendicular_segment(p3)
- >>> l1.is_perpendicular(s1)
- True
- >>> p3 in s1
- True
- >>> l1.perpendicular_segment(Point3D(4, 0, 0))
- Segment3D(Point3D(4, 0, 0), Point3D(4/3, 4/3, 4/3))
- """
- p = Point(p, dim=self.ambient_dimension)
- if p in self:
- return p
- l = self.perpendicular_line(p)
- # The intersection should be unique, so unpack the singleton
- p2, = Intersection(Line(self.p1, self.p2), l)
- return Segment(p, p2)
- @property
- def points(self):
- """The two points used to define this linear entity.
- Returns
- =======
- points : tuple of Points
- See Also
- ========
- sympy.geometry.point.Point
- Examples
- ========
- >>> from sympy import Point, Line
- >>> p1, p2 = Point(0, 0), Point(5, 11)
- >>> l1 = Line(p1, p2)
- >>> l1.points
- (Point2D(0, 0), Point2D(5, 11))
- """
- return (self.p1, self.p2)
- def projection(self, other):
- """Project a point, line, ray, or segment onto this linear entity.
- Parameters
- ==========
- other : Point or LinearEntity (Line, Ray, Segment)
- Returns
- =======
- projection : Point or LinearEntity (Line, Ray, Segment)
- The return type matches the type of the parameter ``other``.
- Raises
- ======
- GeometryError
- When method is unable to perform projection.
- Notes
- =====
- A projection involves taking the two points that define
- the linear entity and projecting those points onto a
- Line and then reforming the linear entity using these
- projections.
- A point P is projected onto a line L by finding the point
- on L that is closest to P. This point is the intersection
- of L and the line perpendicular to L that passes through P.
- See Also
- ========
- sympy.geometry.point.Point, perpendicular_line
- Examples
- ========
- >>> from sympy import Point, Line, Segment, Rational
- >>> p1, p2, p3 = Point(0, 0), Point(1, 1), Point(Rational(1, 2), 0)
- >>> l1 = Line(p1, p2)
- >>> l1.projection(p3)
- Point2D(1/4, 1/4)
- >>> p4, p5 = Point(10, 0), Point(12, 1)
- >>> s1 = Segment(p4, p5)
- >>> l1.projection(s1)
- Segment2D(Point2D(5, 5), Point2D(13/2, 13/2))
- >>> p1, p2, p3 = Point(0, 0, 1), Point(1, 1, 2), Point(2, 0, 1)
- >>> l1 = Line(p1, p2)
- >>> l1.projection(p3)
- Point3D(2/3, 2/3, 5/3)
- >>> p4, p5 = Point(10, 0, 1), Point(12, 1, 3)
- >>> s1 = Segment(p4, p5)
- >>> l1.projection(s1)
- Segment3D(Point3D(10/3, 10/3, 13/3), Point3D(5, 5, 6))
- """
- if not isinstance(other, GeometryEntity):
- other = Point(other, dim=self.ambient_dimension)
- def proj_point(p):
- return Point.project(p - self.p1, self.direction) + self.p1
- if isinstance(other, Point):
- return proj_point(other)
- elif isinstance(other, LinearEntity):
- p1, p2 = proj_point(other.p1), proj_point(other.p2)
- # test to see if we're degenerate
- if p1 == p2:
- return p1
- projected = other.__class__(p1, p2)
- projected = Intersection(self, projected)
- if projected.is_empty:
- return projected
- # if we happen to have intersected in only a point, return that
- if projected.is_FiniteSet and len(projected) == 1:
- # projected is a set of size 1, so unpack it in `a`
- a, = projected
- return a
- # order args so projection is in the same direction as self
- if self.direction.dot(projected.direction) < 0:
- p1, p2 = projected.args
- projected = projected.func(p2, p1)
- return projected
- raise GeometryError(
- "Do not know how to project %s onto %s" % (other, self))
- def random_point(self, seed=None):
- """A random point on a LinearEntity.
- Returns
- =======
- point : Point
- See Also
- ========
- sympy.geometry.point.Point
- Examples
- ========
- >>> from sympy import Point, Line, Ray, Segment
- >>> p1, p2 = Point(0, 0), Point(5, 3)
- >>> line = Line(p1, p2)
- >>> r = line.random_point(seed=42) # seed value is optional
- >>> r.n(3)
- Point2D(-0.72, -0.432)
- >>> r in line
- True
- >>> Ray(p1, p2).random_point(seed=42).n(3)
- Point2D(0.72, 0.432)
- >>> Segment(p1, p2).random_point(seed=42).n(3)
- Point2D(3.2, 1.92)
- """
- if seed is not None:
- rng = random.Random(seed)
- else:
- rng = random
- t = Dummy()
- pt = self.arbitrary_point(t)
- if isinstance(self, Ray):
- v = abs(rng.gauss(0, 1))
- elif isinstance(self, Segment):
- v = rng.random()
- elif isinstance(self, Line):
- v = rng.gauss(0, 1)
- else:
- raise NotImplementedError('unhandled line type')
- return pt.subs(t, Rational(v))
- def bisectors(self, other):
- """Returns the perpendicular lines which pass through the intersections
- of self and other that are in the same plane.
- Parameters
- ==========
- line : Line3D
- Returns
- =======
- list: two Line instances
- Examples
- ========
- >>> from sympy import Point3D, Line3D
- >>> r1 = Line3D(Point3D(0, 0, 0), Point3D(1, 0, 0))
- >>> r2 = Line3D(Point3D(0, 0, 0), Point3D(0, 1, 0))
- >>> r1.bisectors(r2)
- [Line3D(Point3D(0, 0, 0), Point3D(1, 1, 0)), Line3D(Point3D(0, 0, 0), Point3D(1, -1, 0))]
- """
- if not isinstance(other, LinearEntity):
- raise GeometryError("Expecting LinearEntity, not %s" % other)
- l1, l2 = self, other
- # make sure dimensions match or else a warning will rise from
- # intersection calculation
- if l1.p1.ambient_dimension != l2.p1.ambient_dimension:
- if isinstance(l1, Line2D):
- l1, l2 = l2, l1
- _, p1 = Point._normalize_dimension(l1.p1, l2.p1, on_morph='ignore')
- _, p2 = Point._normalize_dimension(l1.p2, l2.p2, on_morph='ignore')
- l2 = Line(p1, p2)
- point = intersection(l1, l2)
- # Three cases: Lines may intersect in a point, may be equal or may not intersect.
- if not point:
- raise GeometryError("The lines do not intersect")
- else:
- pt = point[0]
- if isinstance(pt, Line):
- # Intersection is a line because both lines are coincident
- return [self]
- d1 = l1.direction.unit
- d2 = l2.direction.unit
- bis1 = Line(pt, pt + d1 + d2)
- bis2 = Line(pt, pt + d1 - d2)
- return [bis1, bis2]
- class Line(LinearEntity):
- """An infinite line in space.
- A 2D line is declared with two distinct points, point and slope, or
- an equation. A 3D line may be defined with a point and a direction ratio.
- Parameters
- ==========
- p1 : Point
- p2 : Point
- slope : SymPy expression
- direction_ratio : list
- equation : equation of a line
- Notes
- =====
- `Line` will automatically subclass to `Line2D` or `Line3D` based
- on the dimension of `p1`. The `slope` argument is only relevant
- for `Line2D` and the `direction_ratio` argument is only relevant
- for `Line3D`.
- See Also
- ========
- sympy.geometry.point.Point
- sympy.geometry.line.Line2D
- sympy.geometry.line.Line3D
- Examples
- ========
- >>> from sympy import Line, Segment, Point, Eq
- >>> from sympy.abc import x, y, a, b
- >>> L = Line(Point(2,3), Point(3,5))
- >>> L
- Line2D(Point2D(2, 3), Point2D(3, 5))
- >>> L.points
- (Point2D(2, 3), Point2D(3, 5))
- >>> L.equation()
- -2*x + y + 1
- >>> L.coefficients
- (-2, 1, 1)
- Instantiate with keyword ``slope``:
- >>> Line(Point(0, 0), slope=0)
- Line2D(Point2D(0, 0), Point2D(1, 0))
- Instantiate with another linear object
- >>> s = Segment((0, 0), (0, 1))
- >>> Line(s).equation()
- x
- The line corresponding to an equation in the for `ax + by + c = 0`,
- can be entered:
- >>> Line(3*x + y + 18)
- Line2D(Point2D(0, -18), Point2D(1, -21))
- If `x` or `y` has a different name, then they can be specified, too,
- as a string (to match the name) or symbol:
- >>> Line(Eq(3*a + b, -18), x='a', y=b)
- Line2D(Point2D(0, -18), Point2D(1, -21))
- """
- def __new__(cls, *args, **kwargs):
- if len(args) == 1 and isinstance(args[0], (Expr, Eq)):
- missing = uniquely_named_symbol('?', args)
- if not kwargs:
- x = 'x'
- y = 'y'
- else:
- x = kwargs.pop('x', missing)
- y = kwargs.pop('y', missing)
- if kwargs:
- raise ValueError('expecting only x and y as keywords')
- equation = args[0]
- if isinstance(equation, Eq):
- equation = equation.lhs - equation.rhs
- def find_or_missing(x):
- try:
- return find(x, equation)
- except ValueError:
- return missing
- x = find_or_missing(x)
- y = find_or_missing(y)
- a, b, c = linear_coeffs(equation, x, y)
- if b:
- return Line((0, -c/b), slope=-a/b)
- if a:
- return Line((-c/a, 0), slope=oo)
- raise ValueError('not found in equation: %s' % (set('xy') - {x, y}))
- else:
- if len(args) > 0:
- p1 = args[0]
- if len(args) > 1:
- p2 = args[1]
- else:
- p2 = None
- if isinstance(p1, LinearEntity):
- if p2:
- raise ValueError('If p1 is a LinearEntity, p2 must be None.')
- dim = len(p1.p1)
- else:
- p1 = Point(p1)
- dim = len(p1)
- if p2 is not None or isinstance(p2, Point) and p2.ambient_dimension != dim:
- p2 = Point(p2)
- if dim == 2:
- return Line2D(p1, p2, **kwargs)
- elif dim == 3:
- return Line3D(p1, p2, **kwargs)
- return LinearEntity.__new__(cls, p1, p2, **kwargs)
- def contains(self, other):
- """
- Return True if `other` is on this Line, or False otherwise.
- Examples
- ========
- >>> from sympy import Line,Point
- >>> p1, p2 = Point(0, 1), Point(3, 4)
- >>> l = Line(p1, p2)
- >>> l.contains(p1)
- True
- >>> l.contains((0, 1))
- True
- >>> l.contains((0, 0))
- False
- >>> a = (0, 0, 0)
- >>> b = (1, 1, 1)
- >>> c = (2, 2, 2)
- >>> l1 = Line(a, b)
- >>> l2 = Line(b, a)
- >>> l1 == l2
- False
- >>> l1 in l2
- True
- """
- if not isinstance(other, GeometryEntity):
- other = Point(other, dim=self.ambient_dimension)
- if isinstance(other, Point):
- return Point.is_collinear(other, self.p1, self.p2)
- if isinstance(other, LinearEntity):
- return Point.is_collinear(self.p1, self.p2, other.p1, other.p2)
- return False
- def distance(self, other):
- """
- Finds the shortest distance between a line and a point.
- Raises
- ======
- NotImplementedError is raised if `other` is not a Point
- Examples
- ========
- >>> from sympy import Point, Line
- >>> p1, p2 = Point(0, 0), Point(1, 1)
- >>> s = Line(p1, p2)
- >>> s.distance(Point(-1, 1))
- sqrt(2)
- >>> s.distance((-1, 2))
- 3*sqrt(2)/2
- >>> p1, p2 = Point(0, 0, 0), Point(1, 1, 1)
- >>> s = Line(p1, p2)
- >>> s.distance(Point(-1, 1, 1))
- 2*sqrt(6)/3
- >>> s.distance((-1, 1, 1))
- 2*sqrt(6)/3
- """
- if not isinstance(other, GeometryEntity):
- other = Point(other, dim=self.ambient_dimension)
- if self.contains(other):
- return S.Zero
- return self.perpendicular_segment(other).length
- def equals(self, other):
- """Returns True if self and other are the same mathematical entities"""
- if not isinstance(other, Line):
- return False
- return Point.is_collinear(self.p1, other.p1, self.p2, other.p2)
- def plot_interval(self, parameter='t'):
- """The plot interval for the default geometric plot of line. Gives
- values that will produce a line that is +/- 5 units long (where a
- unit is the distance between the two points that define the line).
- Parameters
- ==========
- parameter : str, optional
- Default value is 't'.
- Returns
- =======
- plot_interval : list (plot interval)
- [parameter, lower_bound, upper_bound]
- Examples
- ========
- >>> from sympy import Point, Line
- >>> p1, p2 = Point(0, 0), Point(5, 3)
- >>> l1 = Line(p1, p2)
- >>> l1.plot_interval()
- [t, -5, 5]
- """
- t = _symbol(parameter, real=True)
- return [t, -5, 5]
- class Ray(LinearEntity):
- """A Ray is a semi-line in the space with a source point and a direction.
- Parameters
- ==========
- p1 : Point
- The source of the Ray
- p2 : Point or radian value
- This point determines the direction in which the Ray propagates.
- If given as an angle it is interpreted in radians with the positive
- direction being ccw.
- Attributes
- ==========
- source
- See Also
- ========
- sympy.geometry.line.Ray2D
- sympy.geometry.line.Ray3D
- sympy.geometry.point.Point
- sympy.geometry.line.Line
- Notes
- =====
- `Ray` will automatically subclass to `Ray2D` or `Ray3D` based on the
- dimension of `p1`.
- Examples
- ========
- >>> from sympy import Ray, Point, pi
- >>> r = Ray(Point(2, 3), Point(3, 5))
- >>> r
- Ray2D(Point2D(2, 3), Point2D(3, 5))
- >>> r.points
- (Point2D(2, 3), Point2D(3, 5))
- >>> r.source
- Point2D(2, 3)
- >>> r.xdirection
- oo
- >>> r.ydirection
- oo
- >>> r.slope
- 2
- >>> Ray(Point(0, 0), angle=pi/4).slope
- 1
- """
- def __new__(cls, p1, p2=None, **kwargs):
- p1 = Point(p1)
- if p2 is not None:
- p1, p2 = Point._normalize_dimension(p1, Point(p2))
- dim = len(p1)
- if dim == 2:
- return Ray2D(p1, p2, **kwargs)
- elif dim == 3:
- return Ray3D(p1, p2, **kwargs)
- return LinearEntity.__new__(cls, p1, p2, **kwargs)
- def _svg(self, scale_factor=1., fill_color="#66cc99"):
- """Returns SVG path element for the LinearEntity.
- Parameters
- ==========
- scale_factor : float
- Multiplication factor for the SVG stroke-width. Default is 1.
- fill_color : str, optional
- Hex string for fill color. Default is "#66cc99".
- """
- verts = (N(self.p1), N(self.p2))
- coords = ["{},{}".format(p.x, p.y) for p in verts]
- path = "M {} L {}".format(coords[0], " L ".join(coords[1:]))
- return (
- '<path fill-rule="evenodd" fill="{2}" stroke="#555555" '
- 'stroke-width="{0}" opacity="0.6" d="{1}" '
- 'marker-start="url(#markerCircle)" marker-end="url(#markerArrow)"/>'
- ).format(2.*scale_factor, path, fill_color)
- def contains(self, other):
- """
- Is other GeometryEntity contained in this Ray?
- Examples
- ========
- >>> from sympy import Ray,Point,Segment
- >>> p1, p2 = Point(0, 0), Point(4, 4)
- >>> r = Ray(p1, p2)
- >>> r.contains(p1)
- True
- >>> r.contains((1, 1))
- True
- >>> r.contains((1, 3))
- False
- >>> s = Segment((1, 1), (2, 2))
- >>> r.contains(s)
- True
- >>> s = Segment((1, 2), (2, 5))
- >>> r.contains(s)
- False
- >>> r1 = Ray((2, 2), (3, 3))
- >>> r.contains(r1)
- True
- >>> r1 = Ray((2, 2), (3, 5))
- >>> r.contains(r1)
- False
- """
- if not isinstance(other, GeometryEntity):
- other = Point(other, dim=self.ambient_dimension)
- if isinstance(other, Point):
- if Point.is_collinear(self.p1, self.p2, other):
- # if we're in the direction of the ray, our
- # direction vector dot the ray's direction vector
- # should be non-negative
- return bool((self.p2 - self.p1).dot(other - self.p1) >= S.Zero)
- return False
- elif isinstance(other, Ray):
- if Point.is_collinear(self.p1, self.p2, other.p1, other.p2):
- return bool((self.p2 - self.p1).dot(other.p2 - other.p1) > S.Zero)
- return False
- elif isinstance(other, Segment):
- return other.p1 in self and other.p2 in self
- # No other known entity can be contained in a Ray
- return False
- def distance(self, other):
- """
- Finds the shortest distance between the ray and a point.
- Raises
- ======
- NotImplementedError is raised if `other` is not a Point
- Examples
- ========
- >>> from sympy import Point, Ray
- >>> p1, p2 = Point(0, 0), Point(1, 1)
- >>> s = Ray(p1, p2)
- >>> s.distance(Point(-1, -1))
- sqrt(2)
- >>> s.distance((-1, 2))
- 3*sqrt(2)/2
- >>> p1, p2 = Point(0, 0, 0), Point(1, 1, 2)
- >>> s = Ray(p1, p2)
- >>> s
- Ray3D(Point3D(0, 0, 0), Point3D(1, 1, 2))
- >>> s.distance(Point(-1, -1, 2))
- 4*sqrt(3)/3
- >>> s.distance((-1, -1, 2))
- 4*sqrt(3)/3
- """
- if not isinstance(other, GeometryEntity):
- other = Point(other, dim=self.ambient_dimension)
- if self.contains(other):
- return S.Zero
- proj = Line(self.p1, self.p2).projection(other)
- if self.contains(proj):
- return abs(other - proj)
- else:
- return abs(other - self.source)
- def equals(self, other):
- """Returns True if self and other are the same mathematical entities"""
- if not isinstance(other, Ray):
- return False
- return self.source == other.source and other.p2 in self
- def plot_interval(self, parameter='t'):
- """The plot interval for the default geometric plot of the Ray. Gives
- values that will produce a ray that is 10 units long (where a unit is
- the distance between the two points that define the ray).
- Parameters
- ==========
- parameter : str, optional
- Default value is 't'.
- Returns
- =======
- plot_interval : list
- [parameter, lower_bound, upper_bound]
- Examples
- ========
- >>> from sympy import Ray, pi
- >>> r = Ray((0, 0), angle=pi/4)
- >>> r.plot_interval()
- [t, 0, 10]
- """
- t = _symbol(parameter, real=True)
- return [t, 0, 10]
- @property
- def source(self):
- """The point from which the ray emanates.
- See Also
- ========
- sympy.geometry.point.Point
- Examples
- ========
- >>> from sympy import Point, Ray
- >>> p1, p2 = Point(0, 0), Point(4, 1)
- >>> r1 = Ray(p1, p2)
- >>> r1.source
- Point2D(0, 0)
- >>> p1, p2 = Point(0, 0, 0), Point(4, 1, 5)
- >>> r1 = Ray(p2, p1)
- >>> r1.source
- Point3D(4, 1, 5)
- """
- return self.p1
- class Segment(LinearEntity):
- """A line segment in space.
- Parameters
- ==========
- p1 : Point
- p2 : Point
- Attributes
- ==========
- length : number or SymPy expression
- midpoint : Point
- See Also
- ========
- sympy.geometry.line.Segment2D
- sympy.geometry.line.Segment3D
- sympy.geometry.point.Point
- sympy.geometry.line.Line
- Notes
- =====
- If 2D or 3D points are used to define `Segment`, it will
- be automatically subclassed to `Segment2D` or `Segment3D`.
- Examples
- ========
- >>> from sympy import Point, Segment
- >>> Segment((1, 0), (1, 1)) # tuples are interpreted as pts
- Segment2D(Point2D(1, 0), Point2D(1, 1))
- >>> s = Segment(Point(4, 3), Point(1, 1))
- >>> s.points
- (Point2D(4, 3), Point2D(1, 1))
- >>> s.slope
- 2/3
- >>> s.length
- sqrt(13)
- >>> s.midpoint
- Point2D(5/2, 2)
- >>> Segment((1, 0, 0), (1, 1, 1)) # tuples are interpreted as pts
- Segment3D(Point3D(1, 0, 0), Point3D(1, 1, 1))
- >>> s = Segment(Point(4, 3, 9), Point(1, 1, 7)); s
- Segment3D(Point3D(4, 3, 9), Point3D(1, 1, 7))
- >>> s.points
- (Point3D(4, 3, 9), Point3D(1, 1, 7))
- >>> s.length
- sqrt(17)
- >>> s.midpoint
- Point3D(5/2, 2, 8)
- """
- def __new__(cls, p1, p2, **kwargs):
- p1, p2 = Point._normalize_dimension(Point(p1), Point(p2))
- dim = len(p1)
- if dim == 2:
- return Segment2D(p1, p2, **kwargs)
- elif dim == 3:
- return Segment3D(p1, p2, **kwargs)
- return LinearEntity.__new__(cls, p1, p2, **kwargs)
- def contains(self, other):
- """
- Is the other GeometryEntity contained within this Segment?
- Examples
- ========
- >>> from sympy import Point, Segment
- >>> p1, p2 = Point(0, 1), Point(3, 4)
- >>> s = Segment(p1, p2)
- >>> s2 = Segment(p2, p1)
- >>> s.contains(s2)
- True
- >>> from sympy import Point3D, Segment3D
- >>> p1, p2 = Point3D(0, 1, 1), Point3D(3, 4, 5)
- >>> s = Segment3D(p1, p2)
- >>> s2 = Segment3D(p2, p1)
- >>> s.contains(s2)
- True
- >>> s.contains((p1 + p2)/2)
- True
- """
- if not isinstance(other, GeometryEntity):
- other = Point(other, dim=self.ambient_dimension)
- if isinstance(other, Point):
- if Point.is_collinear(other, self.p1, self.p2):
- if isinstance(self, Segment2D):
- # if it is collinear and is in the bounding box of the
- # segment then it must be on the segment
- vert = (1/self.slope).equals(0)
- if vert is False:
- isin = (self.p1.x - other.x)*(self.p2.x - other.x) <= 0
- if isin in (True, False):
- return isin
- if vert is True:
- isin = (self.p1.y - other.y)*(self.p2.y - other.y) <= 0
- if isin in (True, False):
- return isin
- # use the triangle inequality
- d1, d2 = other - self.p1, other - self.p2
- d = self.p2 - self.p1
- # without the call to simplify, SymPy cannot tell that an expression
- # like (a+b)*(a/2+b/2) is always non-negative. If it cannot be
- # determined, raise an Undecidable error
- try:
- # the triangle inequality says that |d1|+|d2| >= |d| and is strict
- # only if other lies in the line segment
- return bool(simplify(Eq(abs(d1) + abs(d2) - abs(d), 0)))
- except TypeError:
- raise Undecidable("Cannot determine if {} is in {}".format(other, self))
- if isinstance(other, Segment):
- return other.p1 in self and other.p2 in self
- return False
- def equals(self, other):
- """Returns True if self and other are the same mathematical entities"""
- return isinstance(other, self.func) and list(
- ordered(self.args)) == list(ordered(other.args))
- def distance(self, other):
- """
- Finds the shortest distance between a line segment and a point.
- Raises
- ======
- NotImplementedError is raised if `other` is not a Point
- Examples
- ========
- >>> from sympy import Point, Segment
- >>> p1, p2 = Point(0, 1), Point(3, 4)
- >>> s = Segment(p1, p2)
- >>> s.distance(Point(10, 15))
- sqrt(170)
- >>> s.distance((0, 12))
- sqrt(73)
- >>> from sympy import Point3D, Segment3D
- >>> p1, p2 = Point3D(0, 0, 3), Point3D(1, 1, 4)
- >>> s = Segment3D(p1, p2)
- >>> s.distance(Point3D(10, 15, 12))
- sqrt(341)
- >>> s.distance((10, 15, 12))
- sqrt(341)
- """
- if not isinstance(other, GeometryEntity):
- other = Point(other, dim=self.ambient_dimension)
- if isinstance(other, Point):
- vp1 = other - self.p1
- vp2 = other - self.p2
- dot_prod_sign_1 = self.direction.dot(vp1) >= 0
- dot_prod_sign_2 = self.direction.dot(vp2) <= 0
- if dot_prod_sign_1 and dot_prod_sign_2:
- return Line(self.p1, self.p2).distance(other)
- if dot_prod_sign_1 and not dot_prod_sign_2:
- return abs(vp2)
- if not dot_prod_sign_1 and dot_prod_sign_2:
- return abs(vp1)
- raise NotImplementedError()
- @property
- def length(self):
- """The length of the line segment.
- See Also
- ========
- sympy.geometry.point.Point.distance
- Examples
- ========
- >>> from sympy import Point, Segment
- >>> p1, p2 = Point(0, 0), Point(4, 3)
- >>> s1 = Segment(p1, p2)
- >>> s1.length
- 5
- >>> from sympy import Point3D, Segment3D
- >>> p1, p2 = Point3D(0, 0, 0), Point3D(4, 3, 3)
- >>> s1 = Segment3D(p1, p2)
- >>> s1.length
- sqrt(34)
- """
- return Point.distance(self.p1, self.p2)
- @property
- def midpoint(self):
- """The midpoint of the line segment.
- See Also
- ========
- sympy.geometry.point.Point.midpoint
- Examples
- ========
- >>> from sympy import Point, Segment
- >>> p1, p2 = Point(0, 0), Point(4, 3)
- >>> s1 = Segment(p1, p2)
- >>> s1.midpoint
- Point2D(2, 3/2)
- >>> from sympy import Point3D, Segment3D
- >>> p1, p2 = Point3D(0, 0, 0), Point3D(4, 3, 3)
- >>> s1 = Segment3D(p1, p2)
- >>> s1.midpoint
- Point3D(2, 3/2, 3/2)
- """
- return Point.midpoint(self.p1, self.p2)
- def perpendicular_bisector(self, p=None):
- """The perpendicular bisector of this segment.
- If no point is specified or the point specified is not on the
- bisector then the bisector is returned as a Line. Otherwise a
- Segment is returned that joins the point specified and the
- intersection of the bisector and the segment.
- Parameters
- ==========
- p : Point
- Returns
- =======
- bisector : Line or Segment
- See Also
- ========
- LinearEntity.perpendicular_segment
- Examples
- ========
- >>> from sympy import Point, Segment
- >>> p1, p2, p3 = Point(0, 0), Point(6, 6), Point(5, 1)
- >>> s1 = Segment(p1, p2)
- >>> s1.perpendicular_bisector()
- Line2D(Point2D(3, 3), Point2D(-3, 9))
- >>> s1.perpendicular_bisector(p3)
- Segment2D(Point2D(5, 1), Point2D(3, 3))
- """
- l = self.perpendicular_line(self.midpoint)
- if p is not None:
- p2 = Point(p, dim=self.ambient_dimension)
- if p2 in l:
- return Segment(p2, self.midpoint)
- return l
- def plot_interval(self, parameter='t'):
- """The plot interval for the default geometric plot of the Segment gives
- values that will produce the full segment in a plot.
- Parameters
- ==========
- parameter : str, optional
- Default value is 't'.
- Returns
- =======
- plot_interval : list
- [parameter, lower_bound, upper_bound]
- Examples
- ========
- >>> from sympy import Point, Segment
- >>> p1, p2 = Point(0, 0), Point(5, 3)
- >>> s1 = Segment(p1, p2)
- >>> s1.plot_interval()
- [t, 0, 1]
- """
- t = _symbol(parameter, real=True)
- return [t, 0, 1]
- class LinearEntity2D(LinearEntity):
- """A base class for all linear entities (line, ray and segment)
- in a 2-dimensional Euclidean space.
- Attributes
- ==========
- p1
- p2
- coefficients
- slope
- points
- Notes
- =====
- This is an abstract class and is not meant to be instantiated.
- See Also
- ========
- sympy.geometry.entity.GeometryEntity
- """
- @property
- def bounds(self):
- """Return a tuple (xmin, ymin, xmax, ymax) representing the bounding
- rectangle for the geometric figure.
- """
- verts = self.points
- xs = [p.x for p in verts]
- ys = [p.y for p in verts]
- return (min(xs), min(ys), max(xs), max(ys))
- def perpendicular_line(self, p):
- """Create a new Line perpendicular to this linear entity which passes
- through the point `p`.
- Parameters
- ==========
- p : Point
- Returns
- =======
- line : Line
- See Also
- ========
- sympy.geometry.line.LinearEntity.is_perpendicular, perpendicular_segment
- Examples
- ========
- >>> from sympy import Point, Line
- >>> p1, p2, p3 = Point(0, 0), Point(2, 3), Point(-2, 2)
- >>> l1 = Line(p1, p2)
- >>> l2 = l1.perpendicular_line(p3)
- >>> p3 in l2
- True
- >>> l1.is_perpendicular(l2)
- True
- """
- p = Point(p, dim=self.ambient_dimension)
- # any two lines in R^2 intersect, so blindly making
- # a line through p in an orthogonal direction will work
- return Line(p, p + self.direction.orthogonal_direction)
- @property
- def slope(self):
- """The slope of this linear entity, or infinity if vertical.
- Returns
- =======
- slope : number or SymPy expression
- See Also
- ========
- coefficients
- Examples
- ========
- >>> from sympy import Point, Line
- >>> p1, p2 = Point(0, 0), Point(3, 5)
- >>> l1 = Line(p1, p2)
- >>> l1.slope
- 5/3
- >>> p3 = Point(0, 4)
- >>> l2 = Line(p1, p3)
- >>> l2.slope
- oo
- """
- d1, d2 = (self.p1 - self.p2).args
- if d1 == 0:
- return S.Infinity
- return simplify(d2/d1)
- class Line2D(LinearEntity2D, Line):
- """An infinite line in space 2D.
- A line is declared with two distinct points or a point and slope
- as defined using keyword `slope`.
- Parameters
- ==========
- p1 : Point
- pt : Point
- slope : SymPy expression
- See Also
- ========
- sympy.geometry.point.Point
- Examples
- ========
- >>> from sympy import Line, Segment, Point
- >>> L = Line(Point(2,3), Point(3,5))
- >>> L
- Line2D(Point2D(2, 3), Point2D(3, 5))
- >>> L.points
- (Point2D(2, 3), Point2D(3, 5))
- >>> L.equation()
- -2*x + y + 1
- >>> L.coefficients
- (-2, 1, 1)
- Instantiate with keyword ``slope``:
- >>> Line(Point(0, 0), slope=0)
- Line2D(Point2D(0, 0), Point2D(1, 0))
- Instantiate with another linear object
- >>> s = Segment((0, 0), (0, 1))
- >>> Line(s).equation()
- x
- """
- def __new__(cls, p1, pt=None, slope=None, **kwargs):
- if isinstance(p1, LinearEntity):
- if pt is not None:
- raise ValueError('When p1 is a LinearEntity, pt should be None')
- p1, pt = Point._normalize_dimension(*p1.args, dim=2)
- else:
- p1 = Point(p1, dim=2)
- if pt is not None and slope is None:
- try:
- p2 = Point(pt, dim=2)
- except (NotImplementedError, TypeError, ValueError):
- raise ValueError(filldedent('''
- The 2nd argument was not a valid Point.
- If it was a slope, enter it with keyword "slope".
- '''))
- elif slope is not None and pt is None:
- slope = sympify(slope)
- if slope.is_finite is False:
- # when infinite slope, don't change x
- dx = 0
- dy = 1
- else:
- # go over 1 up slope
- dx = 1
- dy = slope
- # XXX avoiding simplification by adding to coords directly
- p2 = Point(p1.x + dx, p1.y + dy, evaluate=False)
- else:
- raise ValueError('A 2nd Point or keyword "slope" must be used.')
- return LinearEntity2D.__new__(cls, p1, p2, **kwargs)
- def _svg(self, scale_factor=1., fill_color="#66cc99"):
- """Returns SVG path element for the LinearEntity.
- Parameters
- ==========
- scale_factor : float
- Multiplication factor for the SVG stroke-width. Default is 1.
- fill_color : str, optional
- Hex string for fill color. Default is "#66cc99".
- """
- verts = (N(self.p1), N(self.p2))
- coords = ["{},{}".format(p.x, p.y) for p in verts]
- path = "M {} L {}".format(coords[0], " L ".join(coords[1:]))
- return (
- '<path fill-rule="evenodd" fill="{2}" stroke="#555555" '
- 'stroke-width="{0}" opacity="0.6" d="{1}" '
- 'marker-start="url(#markerReverseArrow)" marker-end="url(#markerArrow)"/>'
- ).format(2.*scale_factor, path, fill_color)
- @property
- def coefficients(self):
- """The coefficients (`a`, `b`, `c`) for `ax + by + c = 0`.
- See Also
- ========
- sympy.geometry.line.Line2D.equation
- Examples
- ========
- >>> from sympy import Point, Line
- >>> from sympy.abc import x, y
- >>> p1, p2 = Point(0, 0), Point(5, 3)
- >>> l = Line(p1, p2)
- >>> l.coefficients
- (-3, 5, 0)
- >>> p3 = Point(x, y)
- >>> l2 = Line(p1, p3)
- >>> l2.coefficients
- (-y, x, 0)
- """
- p1, p2 = self.points
- if p1.x == p2.x:
- return (S.One, S.Zero, -p1.x)
- elif p1.y == p2.y:
- return (S.Zero, S.One, -p1.y)
- return tuple([simplify(i) for i in
- (self.p1.y - self.p2.y,
- self.p2.x - self.p1.x,
- self.p1.x*self.p2.y - self.p1.y*self.p2.x)])
- def equation(self, x='x', y='y'):
- """The equation of the line: ax + by + c.
- Parameters
- ==========
- x : str, optional
- The name to use for the x-axis, default value is 'x'.
- y : str, optional
- The name to use for the y-axis, default value is 'y'.
- Returns
- =======
- equation : SymPy expression
- See Also
- ========
- sympy.geometry.line.Line2D.coefficients
- Examples
- ========
- >>> from sympy import Point, Line
- >>> p1, p2 = Point(1, 0), Point(5, 3)
- >>> l1 = Line(p1, p2)
- >>> l1.equation()
- -3*x + 4*y + 3
- """
- x = _symbol(x, real=True)
- y = _symbol(y, real=True)
- p1, p2 = self.points
- if p1.x == p2.x:
- return x - p1.x
- elif p1.y == p2.y:
- return y - p1.y
- a, b, c = self.coefficients
- return a*x + b*y + c
- class Ray2D(LinearEntity2D, Ray):
- """
- A Ray is a semi-line in the space with a source point and a direction.
- Parameters
- ==========
- p1 : Point
- The source of the Ray
- p2 : Point or radian value
- This point determines the direction in which the Ray propagates.
- If given as an angle it is interpreted in radians with the positive
- direction being ccw.
- Attributes
- ==========
- source
- xdirection
- ydirection
- See Also
- ========
- sympy.geometry.point.Point, Line
- Examples
- ========
- >>> from sympy import Point, pi, Ray
- >>> r = Ray(Point(2, 3), Point(3, 5))
- >>> r
- Ray2D(Point2D(2, 3), Point2D(3, 5))
- >>> r.points
- (Point2D(2, 3), Point2D(3, 5))
- >>> r.source
- Point2D(2, 3)
- >>> r.xdirection
- oo
- >>> r.ydirection
- oo
- >>> r.slope
- 2
- >>> Ray(Point(0, 0), angle=pi/4).slope
- 1
- """
- def __new__(cls, p1, pt=None, angle=None, **kwargs):
- p1 = Point(p1, dim=2)
- if pt is not None and angle is None:
- try:
- p2 = Point(pt, dim=2)
- except (NotImplementedError, TypeError, ValueError):
- raise ValueError(filldedent('''
- The 2nd argument was not a valid Point; if
- it was meant to be an angle it should be
- given with keyword "angle".'''))
- if p1 == p2:
- raise ValueError('A Ray requires two distinct points.')
- elif angle is not None and pt is None:
- # we need to know if the angle is an odd multiple of pi/2
- c = pi_coeff(sympify(angle))
- p2 = None
- if c is not None:
- if c.is_Rational:
- if c.q == 2:
- if c.p == 1:
- p2 = p1 + Point(0, 1)
- elif c.p == 3:
- p2 = p1 + Point(0, -1)
- elif c.q == 1:
- if c.p == 0:
- p2 = p1 + Point(1, 0)
- elif c.p == 1:
- p2 = p1 + Point(-1, 0)
- if p2 is None:
- c *= S.Pi
- else:
- c = angle % (2*S.Pi)
- if not p2:
- m = 2*c/S.Pi
- left = And(1 < m, m < 3) # is it in quadrant 2 or 3?
- x = Piecewise((-1, left), (Piecewise((0, Eq(m % 1, 0)), (1, True)), True))
- y = Piecewise((-tan(c), left), (Piecewise((1, Eq(m, 1)), (-1, Eq(m, 3)), (tan(c), True)), True))
- p2 = p1 + Point(x, y)
- else:
- raise ValueError('A 2nd point or keyword "angle" must be used.')
- return LinearEntity2D.__new__(cls, p1, p2, **kwargs)
- @property
- def xdirection(self):
- """The x direction of the ray.
- Positive infinity if the ray points in the positive x direction,
- negative infinity if the ray points in the negative x direction,
- or 0 if the ray is vertical.
- See Also
- ========
- ydirection
- Examples
- ========
- >>> from sympy import Point, Ray
- >>> p1, p2, p3 = Point(0, 0), Point(1, 1), Point(0, -1)
- >>> r1, r2 = Ray(p1, p2), Ray(p1, p3)
- >>> r1.xdirection
- oo
- >>> r2.xdirection
- 0
- """
- if self.p1.x < self.p2.x:
- return S.Infinity
- elif self.p1.x == self.p2.x:
- return S.Zero
- else:
- return S.NegativeInfinity
- @property
- def ydirection(self):
- """The y direction of the ray.
- Positive infinity if the ray points in the positive y direction,
- negative infinity if the ray points in the negative y direction,
- or 0 if the ray is horizontal.
- See Also
- ========
- xdirection
- Examples
- ========
- >>> from sympy import Point, Ray
- >>> p1, p2, p3 = Point(0, 0), Point(-1, -1), Point(-1, 0)
- >>> r1, r2 = Ray(p1, p2), Ray(p1, p3)
- >>> r1.ydirection
- -oo
- >>> r2.ydirection
- 0
- """
- if self.p1.y < self.p2.y:
- return S.Infinity
- elif self.p1.y == self.p2.y:
- return S.Zero
- else:
- return S.NegativeInfinity
- def closing_angle(r1, r2):
- """Return the angle by which r2 must be rotated so it faces the same
- direction as r1.
- Parameters
- ==========
- r1 : Ray2D
- r2 : Ray2D
- Returns
- =======
- angle : angle in radians (ccw angle is positive)
- See Also
- ========
- LinearEntity.angle_between
- Examples
- ========
- >>> from sympy import Ray, pi
- >>> r1 = Ray((0, 0), (1, 0))
- >>> r2 = r1.rotate(-pi/2)
- >>> angle = r1.closing_angle(r2); angle
- pi/2
- >>> r2.rotate(angle).direction.unit == r1.direction.unit
- True
- >>> r2.closing_angle(r1)
- -pi/2
- """
- if not all(isinstance(r, Ray2D) for r in (r1, r2)):
- # although the direction property is defined for
- # all linear entities, only the Ray is truly a
- # directed object
- raise TypeError('Both arguments must be Ray2D objects.')
- a1 = atan2(*list(reversed(r1.direction.args)))
- a2 = atan2(*list(reversed(r2.direction.args)))
- if a1*a2 < 0:
- a1 = 2*S.Pi + a1 if a1 < 0 else a1
- a2 = 2*S.Pi + a2 if a2 < 0 else a2
- return a1 - a2
- class Segment2D(LinearEntity2D, Segment):
- """A line segment in 2D space.
- Parameters
- ==========
- p1 : Point
- p2 : Point
- Attributes
- ==========
- length : number or SymPy expression
- midpoint : Point
- See Also
- ========
- sympy.geometry.point.Point, Line
- Examples
- ========
- >>> from sympy import Point, Segment
- >>> Segment((1, 0), (1, 1)) # tuples are interpreted as pts
- Segment2D(Point2D(1, 0), Point2D(1, 1))
- >>> s = Segment(Point(4, 3), Point(1, 1)); s
- Segment2D(Point2D(4, 3), Point2D(1, 1))
- >>> s.points
- (Point2D(4, 3), Point2D(1, 1))
- >>> s.slope
- 2/3
- >>> s.length
- sqrt(13)
- >>> s.midpoint
- Point2D(5/2, 2)
- """
- def __new__(cls, p1, p2, **kwargs):
- p1 = Point(p1, dim=2)
- p2 = Point(p2, dim=2)
- if p1 == p2:
- return p1
- return LinearEntity2D.__new__(cls, p1, p2, **kwargs)
- def _svg(self, scale_factor=1., fill_color="#66cc99"):
- """Returns SVG path element for the LinearEntity.
- Parameters
- ==========
- scale_factor : float
- Multiplication factor for the SVG stroke-width. Default is 1.
- fill_color : str, optional
- Hex string for fill color. Default is "#66cc99".
- """
- verts = (N(self.p1), N(self.p2))
- coords = ["{},{}".format(p.x, p.y) for p in verts]
- path = "M {} L {}".format(coords[0], " L ".join(coords[1:]))
- return (
- '<path fill-rule="evenodd" fill="{2}" stroke="#555555" '
- 'stroke-width="{0}" opacity="0.6" d="{1}" />'
- ).format(2.*scale_factor, path, fill_color)
- class LinearEntity3D(LinearEntity):
- """An base class for all linear entities (line, ray and segment)
- in a 3-dimensional Euclidean space.
- Attributes
- ==========
- p1
- p2
- direction_ratio
- direction_cosine
- points
- Notes
- =====
- This is a base class and is not meant to be instantiated.
- """
- def __new__(cls, p1, p2, **kwargs):
- p1 = Point3D(p1, dim=3)
- p2 = Point3D(p2, dim=3)
- if p1 == p2:
- # if it makes sense to return a Point, handle in subclass
- raise ValueError(
- "%s.__new__ requires two unique Points." % cls.__name__)
- return GeometryEntity.__new__(cls, p1, p2, **kwargs)
- ambient_dimension = 3
- @property
- def direction_ratio(self):
- """The direction ratio of a given line in 3D.
- See Also
- ========
- sympy.geometry.line.Line3D.equation
- Examples
- ========
- >>> from sympy import Point3D, Line3D
- >>> p1, p2 = Point3D(0, 0, 0), Point3D(5, 3, 1)
- >>> l = Line3D(p1, p2)
- >>> l.direction_ratio
- [5, 3, 1]
- """
- p1, p2 = self.points
- return p1.direction_ratio(p2)
- @property
- def direction_cosine(self):
- """The normalized direction ratio of a given line in 3D.
- See Also
- ========
- sympy.geometry.line.Line3D.equation
- Examples
- ========
- >>> from sympy import Point3D, Line3D
- >>> p1, p2 = Point3D(0, 0, 0), Point3D(5, 3, 1)
- >>> l = Line3D(p1, p2)
- >>> l.direction_cosine
- [sqrt(35)/7, 3*sqrt(35)/35, sqrt(35)/35]
- >>> sum(i**2 for i in _)
- 1
- """
- p1, p2 = self.points
- return p1.direction_cosine(p2)
- class Line3D(LinearEntity3D, Line):
- """An infinite 3D line in space.
- A line is declared with two distinct points or a point and direction_ratio
- as defined using keyword `direction_ratio`.
- Parameters
- ==========
- p1 : Point3D
- pt : Point3D
- direction_ratio : list
- See Also
- ========
- sympy.geometry.point.Point3D
- sympy.geometry.line.Line
- sympy.geometry.line.Line2D
- Examples
- ========
- >>> from sympy import Line3D, Point3D
- >>> L = Line3D(Point3D(2, 3, 4), Point3D(3, 5, 1))
- >>> L
- Line3D(Point3D(2, 3, 4), Point3D(3, 5, 1))
- >>> L.points
- (Point3D(2, 3, 4), Point3D(3, 5, 1))
- """
- def __new__(cls, p1, pt=None, direction_ratio=(), **kwargs):
- if isinstance(p1, LinearEntity3D):
- if pt is not None:
- raise ValueError('if p1 is a LinearEntity, pt must be None.')
- p1, pt = p1.args
- else:
- p1 = Point(p1, dim=3)
- if pt is not None and len(direction_ratio) == 0:
- pt = Point(pt, dim=3)
- elif len(direction_ratio) == 3 and pt is None:
- pt = Point3D(p1.x + direction_ratio[0], p1.y + direction_ratio[1],
- p1.z + direction_ratio[2])
- else:
- raise ValueError('A 2nd Point or keyword "direction_ratio" must '
- 'be used.')
- return LinearEntity3D.__new__(cls, p1, pt, **kwargs)
- def equation(self, x='x', y='y', z='z', k=None):
- """Return the equations that define the line in 3D.
- Parameters
- ==========
- x : str, optional
- The name to use for the x-axis, default value is 'x'.
- y : str, optional
- The name to use for the y-axis, default value is 'y'.
- z : str, optional
- The name to use for the z-axis, default value is 'z'.
- k : str, optional
- .. deprecated:: 1.2
- The ``k`` flag is deprecated. It does nothing.
- Returns
- =======
- equation : Tuple of simultaneous equations
- Examples
- ========
- >>> from sympy import Point3D, Line3D, solve
- >>> from sympy.abc import x, y, z
- >>> p1, p2 = Point3D(1, 0, 0), Point3D(5, 3, 0)
- >>> l1 = Line3D(p1, p2)
- >>> eq = l1.equation(x, y, z); eq
- (-3*x + 4*y + 3, z)
- >>> solve(eq.subs(z, 0), (x, y, z))
- {x: 4*y/3 + 1}
- """
- if k is not None:
- sympy_deprecation_warning(
- """
- The 'k' argument to Line3D.equation() is deprecated. Is
- currently has no effect, so it may be omitted.
- """,
- deprecated_since_version="1.2",
- active_deprecations_target='deprecated-line3d-equation-k',
- )
- from sympy.solvers.solvers import solve
- x, y, z, k = [_symbol(i, real=True) for i in (x, y, z, 'k')]
- p1, p2 = self.points
- d1, d2, d3 = p1.direction_ratio(p2)
- x1, y1, z1 = p1
- eqs = [-d1*k + x - x1, -d2*k + y - y1, -d3*k + z - z1]
- # eliminate k from equations by solving first eq with k for k
- for i, e in enumerate(eqs):
- if e.has(k):
- kk = solve(eqs[i], k)[0]
- eqs.pop(i)
- break
- return Tuple(*[i.subs(k, kk).as_numer_denom()[0] for i in eqs])
- class Ray3D(LinearEntity3D, Ray):
- """
- A Ray is a semi-line in the space with a source point and a direction.
- Parameters
- ==========
- p1 : Point3D
- The source of the Ray
- p2 : Point or a direction vector
- direction_ratio: Determines the direction in which the Ray propagates.
- Attributes
- ==========
- source
- xdirection
- ydirection
- zdirection
- See Also
- ========
- sympy.geometry.point.Point3D, Line3D
- Examples
- ========
- >>> from sympy import Point3D, Ray3D
- >>> r = Ray3D(Point3D(2, 3, 4), Point3D(3, 5, 0))
- >>> r
- Ray3D(Point3D(2, 3, 4), Point3D(3, 5, 0))
- >>> r.points
- (Point3D(2, 3, 4), Point3D(3, 5, 0))
- >>> r.source
- Point3D(2, 3, 4)
- >>> r.xdirection
- oo
- >>> r.ydirection
- oo
- >>> r.direction_ratio
- [1, 2, -4]
- """
- def __new__(cls, p1, pt=None, direction_ratio=(), **kwargs):
- if isinstance(p1, LinearEntity3D):
- if pt is not None:
- raise ValueError('If p1 is a LinearEntity, pt must be None')
- p1, pt = p1.args
- else:
- p1 = Point(p1, dim=3)
- if pt is not None and len(direction_ratio) == 0:
- pt = Point(pt, dim=3)
- elif len(direction_ratio) == 3 and pt is None:
- pt = Point3D(p1.x + direction_ratio[0], p1.y + direction_ratio[1],
- p1.z + direction_ratio[2])
- else:
- raise ValueError(filldedent('''
- A 2nd Point or keyword "direction_ratio" must be used.
- '''))
- return LinearEntity3D.__new__(cls, p1, pt, **kwargs)
- @property
- def xdirection(self):
- """The x direction of the ray.
- Positive infinity if the ray points in the positive x direction,
- negative infinity if the ray points in the negative x direction,
- or 0 if the ray is vertical.
- See Also
- ========
- ydirection
- Examples
- ========
- >>> from sympy import Point3D, Ray3D
- >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(1, 1, 1), Point3D(0, -1, 0)
- >>> r1, r2 = Ray3D(p1, p2), Ray3D(p1, p3)
- >>> r1.xdirection
- oo
- >>> r2.xdirection
- 0
- """
- if self.p1.x < self.p2.x:
- return S.Infinity
- elif self.p1.x == self.p2.x:
- return S.Zero
- else:
- return S.NegativeInfinity
- @property
- def ydirection(self):
- """The y direction of the ray.
- Positive infinity if the ray points in the positive y direction,
- negative infinity if the ray points in the negative y direction,
- or 0 if the ray is horizontal.
- See Also
- ========
- xdirection
- Examples
- ========
- >>> from sympy import Point3D, Ray3D
- >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(-1, -1, -1), Point3D(-1, 0, 0)
- >>> r1, r2 = Ray3D(p1, p2), Ray3D(p1, p3)
- >>> r1.ydirection
- -oo
- >>> r2.ydirection
- 0
- """
- if self.p1.y < self.p2.y:
- return S.Infinity
- elif self.p1.y == self.p2.y:
- return S.Zero
- else:
- return S.NegativeInfinity
- @property
- def zdirection(self):
- """The z direction of the ray.
- Positive infinity if the ray points in the positive z direction,
- negative infinity if the ray points in the negative z direction,
- or 0 if the ray is horizontal.
- See Also
- ========
- xdirection
- Examples
- ========
- >>> from sympy import Point3D, Ray3D
- >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(-1, -1, -1), Point3D(-1, 0, 0)
- >>> r1, r2 = Ray3D(p1, p2), Ray3D(p1, p3)
- >>> r1.ydirection
- -oo
- >>> r2.ydirection
- 0
- >>> r2.zdirection
- 0
- """
- if self.p1.z < self.p2.z:
- return S.Infinity
- elif self.p1.z == self.p2.z:
- return S.Zero
- else:
- return S.NegativeInfinity
- class Segment3D(LinearEntity3D, Segment):
- """A line segment in a 3D space.
- Parameters
- ==========
- p1 : Point3D
- p2 : Point3D
- Attributes
- ==========
- length : number or SymPy expression
- midpoint : Point3D
- See Also
- ========
- sympy.geometry.point.Point3D, Line3D
- Examples
- ========
- >>> from sympy import Point3D, Segment3D
- >>> Segment3D((1, 0, 0), (1, 1, 1)) # tuples are interpreted as pts
- Segment3D(Point3D(1, 0, 0), Point3D(1, 1, 1))
- >>> s = Segment3D(Point3D(4, 3, 9), Point3D(1, 1, 7)); s
- Segment3D(Point3D(4, 3, 9), Point3D(1, 1, 7))
- >>> s.points
- (Point3D(4, 3, 9), Point3D(1, 1, 7))
- >>> s.length
- sqrt(17)
- >>> s.midpoint
- Point3D(5/2, 2, 8)
- """
- def __new__(cls, p1, p2, **kwargs):
- p1 = Point(p1, dim=3)
- p2 = Point(p2, dim=3)
- if p1 == p2:
- return p1
- return LinearEntity3D.__new__(cls, p1, p2, **kwargs)
|