Coverage for /usr/lib/python3/dist-packages/sympy/geometry/line.py: 19%
650 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
1"""Line-like geometrical entities.
3Contains
4========
5LinearEntity
6Line
7Ray
8Segment
9LinearEntity2D
10Line2D
11Ray2D
12Segment2D
13LinearEntity3D
14Line3D
15Ray3D
16Segment3D
18"""
20from sympy.core.containers import Tuple
21from sympy.core.evalf import N
22from sympy.core.expr import Expr
23from sympy.core.numbers import Rational, oo, Float
24from sympy.core.relational import Eq
25from sympy.core.singleton import S
26from sympy.core.sorting import ordered
27from sympy.core.symbol import _symbol, Dummy, uniquely_named_symbol
28from sympy.core.sympify import sympify
29from sympy.functions.elementary.piecewise import Piecewise
30from sympy.functions.elementary.trigonometric import (_pi_coeff, acos, tan, atan2)
31from .entity import GeometryEntity, GeometrySet
32from .exceptions import GeometryError
33from .point import Point, Point3D
34from .util import find, intersection
35from sympy.logic.boolalg import And
36from sympy.matrices import Matrix
37from sympy.sets.sets import Intersection
38from sympy.simplify.simplify import simplify
39from sympy.solvers.solvers import solve
40from sympy.solvers.solveset import linear_coeffs
41from sympy.utilities.misc import Undecidable, filldedent
44import random
47t, u = [Dummy('line_dummy') for i in range(2)]
50class LinearEntity(GeometrySet):
51 """A base class for all linear entities (Line, Ray and Segment)
52 in n-dimensional Euclidean space.
54 Attributes
55 ==========
57 ambient_dimension
58 direction
59 length
60 p1
61 p2
62 points
64 Notes
65 =====
67 This is an abstract class and is not meant to be instantiated.
69 See Also
70 ========
72 sympy.geometry.entity.GeometryEntity
74 """
75 def __new__(cls, p1, p2=None, **kwargs):
76 p1, p2 = Point._normalize_dimension(p1, p2)
77 if p1 == p2:
78 # sometimes we return a single point if we are not given two unique
79 # points. This is done in the specific subclass
80 raise ValueError(
81 "%s.__new__ requires two unique Points." % cls.__name__)
82 if len(p1) != len(p2):
83 raise ValueError(
84 "%s.__new__ requires two Points of equal dimension." % cls.__name__)
86 return GeometryEntity.__new__(cls, p1, p2, **kwargs)
88 def __contains__(self, other):
89 """Return a definitive answer or else raise an error if it cannot
90 be determined that other is on the boundaries of self."""
91 result = self.contains(other)
93 if result is not None:
94 return result
95 else:
96 raise Undecidable(
97 "Cannot decide whether '%s' contains '%s'" % (self, other))
99 def _span_test(self, other):
100 """Test whether the point `other` lies in the positive span of `self`.
101 A point x is 'in front' of a point y if x.dot(y) >= 0. Return
102 -1 if `other` is behind `self.p1`, 0 if `other` is `self.p1` and
103 and 1 if `other` is in front of `self.p1`."""
104 if self.p1 == other:
105 return 0
107 rel_pos = other - self.p1
108 d = self.direction
109 if d.dot(rel_pos) > 0:
110 return 1
111 return -1
113 @property
114 def ambient_dimension(self):
115 """A property method that returns the dimension of LinearEntity
116 object.
118 Parameters
119 ==========
121 p1 : LinearEntity
123 Returns
124 =======
126 dimension : integer
128 Examples
129 ========
131 >>> from sympy import Point, Line
132 >>> p1, p2 = Point(0, 0), Point(1, 1)
133 >>> l1 = Line(p1, p2)
134 >>> l1.ambient_dimension
135 2
137 >>> from sympy import Point, Line
138 >>> p1, p2 = Point(0, 0, 0), Point(1, 1, 1)
139 >>> l1 = Line(p1, p2)
140 >>> l1.ambient_dimension
141 3
143 """
144 return len(self.p1)
146 def angle_between(l1, l2):
147 """Return the non-reflex angle formed by rays emanating from
148 the origin with directions the same as the direction vectors
149 of the linear entities.
151 Parameters
152 ==========
154 l1 : LinearEntity
155 l2 : LinearEntity
157 Returns
158 =======
160 angle : angle in radians
162 Notes
163 =====
165 From the dot product of vectors v1 and v2 it is known that:
167 ``dot(v1, v2) = |v1|*|v2|*cos(A)``
169 where A is the angle formed between the two vectors. We can
170 get the directional vectors of the two lines and readily
171 find the angle between the two using the above formula.
173 See Also
174 ========
176 is_perpendicular, Ray2D.closing_angle
178 Examples
179 ========
181 >>> from sympy import Line
182 >>> e = Line((0, 0), (1, 0))
183 >>> ne = Line((0, 0), (1, 1))
184 >>> sw = Line((1, 1), (0, 0))
185 >>> ne.angle_between(e)
186 pi/4
187 >>> sw.angle_between(e)
188 3*pi/4
190 To obtain the non-obtuse angle at the intersection of lines, use
191 the ``smallest_angle_between`` method:
193 >>> sw.smallest_angle_between(e)
194 pi/4
196 >>> from sympy import Point3D, Line3D
197 >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(1, 1, 1), Point3D(-1, 2, 0)
198 >>> l1, l2 = Line3D(p1, p2), Line3D(p2, p3)
199 >>> l1.angle_between(l2)
200 acos(-sqrt(2)/3)
201 >>> l1.smallest_angle_between(l2)
202 acos(sqrt(2)/3)
203 """
204 if not isinstance(l1, LinearEntity) and not isinstance(l2, LinearEntity):
205 raise TypeError('Must pass only LinearEntity objects')
207 v1, v2 = l1.direction, l2.direction
208 return acos(v1.dot(v2)/(abs(v1)*abs(v2)))
210 def smallest_angle_between(l1, l2):
211 """Return the smallest angle formed at the intersection of the
212 lines containing the linear entities.
214 Parameters
215 ==========
217 l1 : LinearEntity
218 l2 : LinearEntity
220 Returns
221 =======
223 angle : angle in radians
225 Examples
226 ========
228 >>> from sympy import Point, Line
229 >>> p1, p2, p3 = Point(0, 0), Point(0, 4), Point(2, -2)
230 >>> l1, l2 = Line(p1, p2), Line(p1, p3)
231 >>> l1.smallest_angle_between(l2)
232 pi/4
234 See Also
235 ========
237 angle_between, is_perpendicular, Ray2D.closing_angle
238 """
239 if not isinstance(l1, LinearEntity) and not isinstance(l2, LinearEntity):
240 raise TypeError('Must pass only LinearEntity objects')
242 v1, v2 = l1.direction, l2.direction
243 return acos(abs(v1.dot(v2))/(abs(v1)*abs(v2)))
245 def arbitrary_point(self, parameter='t'):
246 """A parameterized point on the Line.
248 Parameters
249 ==========
251 parameter : str, optional
252 The name of the parameter which will be used for the parametric
253 point. The default value is 't'. When this parameter is 0, the
254 first point used to define the line will be returned, and when
255 it is 1 the second point will be returned.
257 Returns
258 =======
260 point : Point
262 Raises
263 ======
265 ValueError
266 When ``parameter`` already appears in the Line's definition.
268 See Also
269 ========
271 sympy.geometry.point.Point
273 Examples
274 ========
276 >>> from sympy import Point, Line
277 >>> p1, p2 = Point(1, 0), Point(5, 3)
278 >>> l1 = Line(p1, p2)
279 >>> l1.arbitrary_point()
280 Point2D(4*t + 1, 3*t)
281 >>> from sympy import Point3D, Line3D
282 >>> p1, p2 = Point3D(1, 0, 0), Point3D(5, 3, 1)
283 >>> l1 = Line3D(p1, p2)
284 >>> l1.arbitrary_point()
285 Point3D(4*t + 1, 3*t, t)
287 """
288 t = _symbol(parameter, real=True)
289 if t.name in (f.name for f in self.free_symbols):
290 raise ValueError(filldedent('''
291 Symbol %s already appears in object
292 and cannot be used as a parameter.
293 ''' % t.name))
294 # multiply on the right so the variable gets
295 # combined with the coordinates of the point
296 return self.p1 + (self.p2 - self.p1)*t
298 @staticmethod
299 def are_concurrent(*lines):
300 """Is a sequence of linear entities concurrent?
302 Two or more linear entities are concurrent if they all
303 intersect at a single point.
305 Parameters
306 ==========
308 lines
309 A sequence of linear entities.
311 Returns
312 =======
314 True : if the set of linear entities intersect in one point
315 False : otherwise.
317 See Also
318 ========
320 sympy.geometry.util.intersection
322 Examples
323 ========
325 >>> from sympy import Point, Line
326 >>> p1, p2 = Point(0, 0), Point(3, 5)
327 >>> p3, p4 = Point(-2, -2), Point(0, 2)
328 >>> l1, l2, l3 = Line(p1, p2), Line(p1, p3), Line(p1, p4)
329 >>> Line.are_concurrent(l1, l2, l3)
330 True
331 >>> l4 = Line(p2, p3)
332 >>> Line.are_concurrent(l2, l3, l4)
333 False
334 >>> from sympy import Point3D, Line3D
335 >>> p1, p2 = Point3D(0, 0, 0), Point3D(3, 5, 2)
336 >>> p3, p4 = Point3D(-2, -2, -2), Point3D(0, 2, 1)
337 >>> l1, l2, l3 = Line3D(p1, p2), Line3D(p1, p3), Line3D(p1, p4)
338 >>> Line3D.are_concurrent(l1, l2, l3)
339 True
340 >>> l4 = Line3D(p2, p3)
341 >>> Line3D.are_concurrent(l2, l3, l4)
342 False
344 """
345 common_points = Intersection(*lines)
346 if common_points.is_FiniteSet and len(common_points) == 1:
347 return True
348 return False
350 def contains(self, other):
351 """Subclasses should implement this method and should return
352 True if other is on the boundaries of self;
353 False if not on the boundaries of self;
354 None if a determination cannot be made."""
355 raise NotImplementedError()
357 @property
358 def direction(self):
359 """The direction vector of the LinearEntity.
361 Returns
362 =======
364 p : a Point; the ray from the origin to this point is the
365 direction of `self`
367 Examples
368 ========
370 >>> from sympy import Line
371 >>> a, b = (1, 1), (1, 3)
372 >>> Line(a, b).direction
373 Point2D(0, 2)
374 >>> Line(b, a).direction
375 Point2D(0, -2)
377 This can be reported so the distance from the origin is 1:
379 >>> Line(b, a).direction.unit
380 Point2D(0, -1)
382 See Also
383 ========
385 sympy.geometry.point.Point.unit
387 """
388 return self.p2 - self.p1
390 def intersection(self, other):
391 """The intersection with another geometrical entity.
393 Parameters
394 ==========
396 o : Point or LinearEntity
398 Returns
399 =======
401 intersection : list of geometrical entities
403 See Also
404 ========
406 sympy.geometry.point.Point
408 Examples
409 ========
411 >>> from sympy import Point, Line, Segment
412 >>> p1, p2, p3 = Point(0, 0), Point(1, 1), Point(7, 7)
413 >>> l1 = Line(p1, p2)
414 >>> l1.intersection(p3)
415 [Point2D(7, 7)]
416 >>> p4, p5 = Point(5, 0), Point(0, 3)
417 >>> l2 = Line(p4, p5)
418 >>> l1.intersection(l2)
419 [Point2D(15/8, 15/8)]
420 >>> p6, p7 = Point(0, 5), Point(2, 6)
421 >>> s1 = Segment(p6, p7)
422 >>> l1.intersection(s1)
423 []
424 >>> from sympy import Point3D, Line3D, Segment3D
425 >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(1, 1, 1), Point3D(7, 7, 7)
426 >>> l1 = Line3D(p1, p2)
427 >>> l1.intersection(p3)
428 [Point3D(7, 7, 7)]
429 >>> l1 = Line3D(Point3D(4,19,12), Point3D(5,25,17))
430 >>> l2 = Line3D(Point3D(-3, -15, -19), direction_ratio=[2,8,8])
431 >>> l1.intersection(l2)
432 [Point3D(1, 1, -3)]
433 >>> p6, p7 = Point3D(0, 5, 2), Point3D(2, 6, 3)
434 >>> s1 = Segment3D(p6, p7)
435 >>> l1.intersection(s1)
436 []
438 """
439 def intersect_parallel_rays(ray1, ray2):
440 if ray1.direction.dot(ray2.direction) > 0:
441 # rays point in the same direction
442 # so return the one that is "in front"
443 return [ray2] if ray1._span_test(ray2.p1) >= 0 else [ray1]
444 else:
445 # rays point in opposite directions
446 st = ray1._span_test(ray2.p1)
447 if st < 0:
448 return []
449 elif st == 0:
450 return [ray2.p1]
451 return [Segment(ray1.p1, ray2.p1)]
453 def intersect_parallel_ray_and_segment(ray, seg):
454 st1, st2 = ray._span_test(seg.p1), ray._span_test(seg.p2)
455 if st1 < 0 and st2 < 0:
456 return []
457 elif st1 >= 0 and st2 >= 0:
458 return [seg]
459 elif st1 >= 0: # st2 < 0:
460 return [Segment(ray.p1, seg.p1)]
461 else: # st1 < 0 and st2 >= 0:
462 return [Segment(ray.p1, seg.p2)]
464 def intersect_parallel_segments(seg1, seg2):
465 if seg1.contains(seg2):
466 return [seg2]
467 if seg2.contains(seg1):
468 return [seg1]
470 # direct the segments so they're oriented the same way
471 if seg1.direction.dot(seg2.direction) < 0:
472 seg2 = Segment(seg2.p2, seg2.p1)
473 # order the segments so seg1 is "behind" seg2
474 if seg1._span_test(seg2.p1) < 0:
475 seg1, seg2 = seg2, seg1
476 if seg2._span_test(seg1.p2) < 0:
477 return []
478 return [Segment(seg2.p1, seg1.p2)]
480 if not isinstance(other, GeometryEntity):
481 other = Point(other, dim=self.ambient_dimension)
482 if other.is_Point:
483 if self.contains(other):
484 return [other]
485 else:
486 return []
487 elif isinstance(other, LinearEntity):
488 # break into cases based on whether
489 # the lines are parallel, non-parallel intersecting, or skew
490 pts = Point._normalize_dimension(self.p1, self.p2, other.p1, other.p2)
491 rank = Point.affine_rank(*pts)
493 if rank == 1:
494 # we're collinear
495 if isinstance(self, Line):
496 return [other]
497 if isinstance(other, Line):
498 return [self]
500 if isinstance(self, Ray) and isinstance(other, Ray):
501 return intersect_parallel_rays(self, other)
502 if isinstance(self, Ray) and isinstance(other, Segment):
503 return intersect_parallel_ray_and_segment(self, other)
504 if isinstance(self, Segment) and isinstance(other, Ray):
505 return intersect_parallel_ray_and_segment(other, self)
506 if isinstance(self, Segment) and isinstance(other, Segment):
507 return intersect_parallel_segments(self, other)
508 elif rank == 2:
509 # we're in the same plane
510 l1 = Line(*pts[:2])
511 l2 = Line(*pts[2:])
513 # check to see if we're parallel. If we are, we can't
514 # be intersecting, since the collinear case was already
515 # handled
516 if l1.direction.is_scalar_multiple(l2.direction):
517 return []
519 # find the intersection as if everything were lines
520 # by solving the equation t*d + p1 == s*d' + p1'
521 m = Matrix([l1.direction, -l2.direction]).transpose()
522 v = Matrix([l2.p1 - l1.p1]).transpose()
524 # we cannot use m.solve(v) because that only works for square matrices
525 m_rref, pivots = m.col_insert(2, v).rref(simplify=True)
526 # rank == 2 ensures we have 2 pivots, but let's check anyway
527 if len(pivots) != 2:
528 raise GeometryError("Failed when solving Mx=b when M={} and b={}".format(m, v))
529 coeff = m_rref[0, 2]
530 line_intersection = l1.direction*coeff + self.p1
532 # if both are lines, skip a containment check
533 if isinstance(self, Line) and isinstance(other, Line):
534 return [line_intersection]
536 if ((isinstance(self, Line) or
537 self.contains(line_intersection)) and
538 other.contains(line_intersection)):
539 return [line_intersection]
540 if not self.atoms(Float) and not other.atoms(Float):
541 # if it can fail when there are no Floats then
542 # maybe the following parametric check should be
543 # done
544 return []
545 # floats may fail exact containment so check that the
546 # arbitrary points, when equal, both give a
547 # non-negative parameter when the arbitrary point
548 # coordinates are equated
549 tu = solve(self.arbitrary_point(t) - other.arbitrary_point(u),
550 t, u, dict=True)[0]
551 def ok(p, l):
552 if isinstance(l, Line):
553 # p > -oo
554 return True
555 if isinstance(l, Ray):
556 # p >= 0
557 return p.is_nonnegative
558 if isinstance(l, Segment):
559 # 0 <= p <= 1
560 return p.is_nonnegative and (1 - p).is_nonnegative
561 raise ValueError("unexpected line type")
562 if ok(tu[t], self) and ok(tu[u], other):
563 return [line_intersection]
564 return []
565 else:
566 # we're skew
567 return []
569 return other.intersection(self)
571 def is_parallel(l1, l2):
572 """Are two linear entities parallel?
574 Parameters
575 ==========
577 l1 : LinearEntity
578 l2 : LinearEntity
580 Returns
581 =======
583 True : if l1 and l2 are parallel,
584 False : otherwise.
586 See Also
587 ========
589 coefficients
591 Examples
592 ========
594 >>> from sympy import Point, Line
595 >>> p1, p2 = Point(0, 0), Point(1, 1)
596 >>> p3, p4 = Point(3, 4), Point(6, 7)
597 >>> l1, l2 = Line(p1, p2), Line(p3, p4)
598 >>> Line.is_parallel(l1, l2)
599 True
600 >>> p5 = Point(6, 6)
601 >>> l3 = Line(p3, p5)
602 >>> Line.is_parallel(l1, l3)
603 False
604 >>> from sympy import Point3D, Line3D
605 >>> p1, p2 = Point3D(0, 0, 0), Point3D(3, 4, 5)
606 >>> p3, p4 = Point3D(2, 1, 1), Point3D(8, 9, 11)
607 >>> l1, l2 = Line3D(p1, p2), Line3D(p3, p4)
608 >>> Line3D.is_parallel(l1, l2)
609 True
610 >>> p5 = Point3D(6, 6, 6)
611 >>> l3 = Line3D(p3, p5)
612 >>> Line3D.is_parallel(l1, l3)
613 False
615 """
616 if not isinstance(l1, LinearEntity) and not isinstance(l2, LinearEntity):
617 raise TypeError('Must pass only LinearEntity objects')
619 return l1.direction.is_scalar_multiple(l2.direction)
621 def is_perpendicular(l1, l2):
622 """Are two linear entities perpendicular?
624 Parameters
625 ==========
627 l1 : LinearEntity
628 l2 : LinearEntity
630 Returns
631 =======
633 True : if l1 and l2 are perpendicular,
634 False : otherwise.
636 See Also
637 ========
639 coefficients
641 Examples
642 ========
644 >>> from sympy import Point, Line
645 >>> p1, p2, p3 = Point(0, 0), Point(1, 1), Point(-1, 1)
646 >>> l1, l2 = Line(p1, p2), Line(p1, p3)
647 >>> l1.is_perpendicular(l2)
648 True
649 >>> p4 = Point(5, 3)
650 >>> l3 = Line(p1, p4)
651 >>> l1.is_perpendicular(l3)
652 False
653 >>> from sympy import Point3D, Line3D
654 >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(1, 1, 1), Point3D(-1, 2, 0)
655 >>> l1, l2 = Line3D(p1, p2), Line3D(p2, p3)
656 >>> l1.is_perpendicular(l2)
657 False
658 >>> p4 = Point3D(5, 3, 7)
659 >>> l3 = Line3D(p1, p4)
660 >>> l1.is_perpendicular(l3)
661 False
663 """
664 if not isinstance(l1, LinearEntity) and not isinstance(l2, LinearEntity):
665 raise TypeError('Must pass only LinearEntity objects')
667 return S.Zero.equals(l1.direction.dot(l2.direction))
669 def is_similar(self, other):
670 """
671 Return True if self and other are contained in the same line.
673 Examples
674 ========
676 >>> from sympy import Point, Line
677 >>> p1, p2, p3 = Point(0, 1), Point(3, 4), Point(2, 3)
678 >>> l1 = Line(p1, p2)
679 >>> l2 = Line(p1, p3)
680 >>> l1.is_similar(l2)
681 True
682 """
683 l = Line(self.p1, self.p2)
684 return l.contains(other)
686 @property
687 def length(self):
688 """
689 The length of the line.
691 Examples
692 ========
694 >>> from sympy import Point, Line
695 >>> p1, p2 = Point(0, 0), Point(3, 5)
696 >>> l1 = Line(p1, p2)
697 >>> l1.length
698 oo
699 """
700 return S.Infinity
702 @property
703 def p1(self):
704 """The first defining point of a linear entity.
706 See Also
707 ========
709 sympy.geometry.point.Point
711 Examples
712 ========
714 >>> from sympy import Point, Line
715 >>> p1, p2 = Point(0, 0), Point(5, 3)
716 >>> l = Line(p1, p2)
717 >>> l.p1
718 Point2D(0, 0)
720 """
721 return self.args[0]
723 @property
724 def p2(self):
725 """The second defining point of a linear entity.
727 See Also
728 ========
730 sympy.geometry.point.Point
732 Examples
733 ========
735 >>> from sympy import Point, Line
736 >>> p1, p2 = Point(0, 0), Point(5, 3)
737 >>> l = Line(p1, p2)
738 >>> l.p2
739 Point2D(5, 3)
741 """
742 return self.args[1]
744 def parallel_line(self, p):
745 """Create a new Line parallel to this linear entity which passes
746 through the point `p`.
748 Parameters
749 ==========
751 p : Point
753 Returns
754 =======
756 line : Line
758 See Also
759 ========
761 is_parallel
763 Examples
764 ========
766 >>> from sympy import Point, Line
767 >>> p1, p2, p3 = Point(0, 0), Point(2, 3), Point(-2, 2)
768 >>> l1 = Line(p1, p2)
769 >>> l2 = l1.parallel_line(p3)
770 >>> p3 in l2
771 True
772 >>> l1.is_parallel(l2)
773 True
774 >>> from sympy import Point3D, Line3D
775 >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(2, 3, 4), Point3D(-2, 2, 0)
776 >>> l1 = Line3D(p1, p2)
777 >>> l2 = l1.parallel_line(p3)
778 >>> p3 in l2
779 True
780 >>> l1.is_parallel(l2)
781 True
783 """
784 p = Point(p, dim=self.ambient_dimension)
785 return Line(p, p + self.direction)
787 def perpendicular_line(self, p):
788 """Create a new Line perpendicular to this linear entity which passes
789 through the point `p`.
791 Parameters
792 ==========
794 p : Point
796 Returns
797 =======
799 line : Line
801 See Also
802 ========
804 sympy.geometry.line.LinearEntity.is_perpendicular, perpendicular_segment
806 Examples
807 ========
809 >>> from sympy import Point3D, Line3D
810 >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(2, 3, 4), Point3D(-2, 2, 0)
811 >>> L = Line3D(p1, p2)
812 >>> P = L.perpendicular_line(p3); P
813 Line3D(Point3D(-2, 2, 0), Point3D(4/29, 6/29, 8/29))
814 >>> L.is_perpendicular(P)
815 True
817 In 3D the, the first point used to define the line is the point
818 through which the perpendicular was required to pass; the
819 second point is (arbitrarily) contained in the given line:
821 >>> P.p2 in L
822 True
823 """
824 p = Point(p, dim=self.ambient_dimension)
825 if p in self:
826 p = p + self.direction.orthogonal_direction
827 return Line(p, self.projection(p))
829 def perpendicular_segment(self, p):
830 """Create a perpendicular line segment from `p` to this line.
832 The endpoints of the segment are ``p`` and the closest point in
833 the line containing self. (If self is not a line, the point might
834 not be in self.)
836 Parameters
837 ==========
839 p : Point
841 Returns
842 =======
844 segment : Segment
846 Notes
847 =====
849 Returns `p` itself if `p` is on this linear entity.
851 See Also
852 ========
854 perpendicular_line
856 Examples
857 ========
859 >>> from sympy import Point, Line
860 >>> p1, p2, p3 = Point(0, 0), Point(1, 1), Point(0, 2)
861 >>> l1 = Line(p1, p2)
862 >>> s1 = l1.perpendicular_segment(p3)
863 >>> l1.is_perpendicular(s1)
864 True
865 >>> p3 in s1
866 True
867 >>> l1.perpendicular_segment(Point(4, 0))
868 Segment2D(Point2D(4, 0), Point2D(2, 2))
869 >>> from sympy import Point3D, Line3D
870 >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(1, 1, 1), Point3D(0, 2, 0)
871 >>> l1 = Line3D(p1, p2)
872 >>> s1 = l1.perpendicular_segment(p3)
873 >>> l1.is_perpendicular(s1)
874 True
875 >>> p3 in s1
876 True
877 >>> l1.perpendicular_segment(Point3D(4, 0, 0))
878 Segment3D(Point3D(4, 0, 0), Point3D(4/3, 4/3, 4/3))
880 """
881 p = Point(p, dim=self.ambient_dimension)
882 if p in self:
883 return p
884 l = self.perpendicular_line(p)
885 # The intersection should be unique, so unpack the singleton
886 p2, = Intersection(Line(self.p1, self.p2), l)
888 return Segment(p, p2)
890 @property
891 def points(self):
892 """The two points used to define this linear entity.
894 Returns
895 =======
897 points : tuple of Points
899 See Also
900 ========
902 sympy.geometry.point.Point
904 Examples
905 ========
907 >>> from sympy import Point, Line
908 >>> p1, p2 = Point(0, 0), Point(5, 11)
909 >>> l1 = Line(p1, p2)
910 >>> l1.points
911 (Point2D(0, 0), Point2D(5, 11))
913 """
914 return (self.p1, self.p2)
916 def projection(self, other):
917 """Project a point, line, ray, or segment onto this linear entity.
919 Parameters
920 ==========
922 other : Point or LinearEntity (Line, Ray, Segment)
924 Returns
925 =======
927 projection : Point or LinearEntity (Line, Ray, Segment)
928 The return type matches the type of the parameter ``other``.
930 Raises
931 ======
933 GeometryError
934 When method is unable to perform projection.
936 Notes
937 =====
939 A projection involves taking the two points that define
940 the linear entity and projecting those points onto a
941 Line and then reforming the linear entity using these
942 projections.
943 A point P is projected onto a line L by finding the point
944 on L that is closest to P. This point is the intersection
945 of L and the line perpendicular to L that passes through P.
947 See Also
948 ========
950 sympy.geometry.point.Point, perpendicular_line
952 Examples
953 ========
955 >>> from sympy import Point, Line, Segment, Rational
956 >>> p1, p2, p3 = Point(0, 0), Point(1, 1), Point(Rational(1, 2), 0)
957 >>> l1 = Line(p1, p2)
958 >>> l1.projection(p3)
959 Point2D(1/4, 1/4)
960 >>> p4, p5 = Point(10, 0), Point(12, 1)
961 >>> s1 = Segment(p4, p5)
962 >>> l1.projection(s1)
963 Segment2D(Point2D(5, 5), Point2D(13/2, 13/2))
964 >>> p1, p2, p3 = Point(0, 0, 1), Point(1, 1, 2), Point(2, 0, 1)
965 >>> l1 = Line(p1, p2)
966 >>> l1.projection(p3)
967 Point3D(2/3, 2/3, 5/3)
968 >>> p4, p5 = Point(10, 0, 1), Point(12, 1, 3)
969 >>> s1 = Segment(p4, p5)
970 >>> l1.projection(s1)
971 Segment3D(Point3D(10/3, 10/3, 13/3), Point3D(5, 5, 6))
973 """
974 if not isinstance(other, GeometryEntity):
975 other = Point(other, dim=self.ambient_dimension)
977 def proj_point(p):
978 return Point.project(p - self.p1, self.direction) + self.p1
980 if isinstance(other, Point):
981 return proj_point(other)
982 elif isinstance(other, LinearEntity):
983 p1, p2 = proj_point(other.p1), proj_point(other.p2)
984 # test to see if we're degenerate
985 if p1 == p2:
986 return p1
987 projected = other.__class__(p1, p2)
988 projected = Intersection(self, projected)
989 if projected.is_empty:
990 return projected
991 # if we happen to have intersected in only a point, return that
992 if projected.is_FiniteSet and len(projected) == 1:
993 # projected is a set of size 1, so unpack it in `a`
994 a, = projected
995 return a
996 # order args so projection is in the same direction as self
997 if self.direction.dot(projected.direction) < 0:
998 p1, p2 = projected.args
999 projected = projected.func(p2, p1)
1000 return projected
1002 raise GeometryError(
1003 "Do not know how to project %s onto %s" % (other, self))
1005 def random_point(self, seed=None):
1006 """A random point on a LinearEntity.
1008 Returns
1009 =======
1011 point : Point
1013 See Also
1014 ========
1016 sympy.geometry.point.Point
1018 Examples
1019 ========
1021 >>> from sympy import Point, Line, Ray, Segment
1022 >>> p1, p2 = Point(0, 0), Point(5, 3)
1023 >>> line = Line(p1, p2)
1024 >>> r = line.random_point(seed=42) # seed value is optional
1025 >>> r.n(3)
1026 Point2D(-0.72, -0.432)
1027 >>> r in line
1028 True
1029 >>> Ray(p1, p2).random_point(seed=42).n(3)
1030 Point2D(0.72, 0.432)
1031 >>> Segment(p1, p2).random_point(seed=42).n(3)
1032 Point2D(3.2, 1.92)
1034 """
1035 if seed is not None:
1036 rng = random.Random(seed)
1037 else:
1038 rng = random
1039 pt = self.arbitrary_point(t)
1040 if isinstance(self, Ray):
1041 v = abs(rng.gauss(0, 1))
1042 elif isinstance(self, Segment):
1043 v = rng.random()
1044 elif isinstance(self, Line):
1045 v = rng.gauss(0, 1)
1046 else:
1047 raise NotImplementedError('unhandled line type')
1048 return pt.subs(t, Rational(v))
1050 def bisectors(self, other):
1051 """Returns the perpendicular lines which pass through the intersections
1052 of self and other that are in the same plane.
1054 Parameters
1055 ==========
1057 line : Line3D
1059 Returns
1060 =======
1062 list: two Line instances
1064 Examples
1065 ========
1067 >>> from sympy import Point3D, Line3D
1068 >>> r1 = Line3D(Point3D(0, 0, 0), Point3D(1, 0, 0))
1069 >>> r2 = Line3D(Point3D(0, 0, 0), Point3D(0, 1, 0))
1070 >>> r1.bisectors(r2)
1071 [Line3D(Point3D(0, 0, 0), Point3D(1, 1, 0)), Line3D(Point3D(0, 0, 0), Point3D(1, -1, 0))]
1073 """
1074 if not isinstance(other, LinearEntity):
1075 raise GeometryError("Expecting LinearEntity, not %s" % other)
1077 l1, l2 = self, other
1079 # make sure dimensions match or else a warning will rise from
1080 # intersection calculation
1081 if l1.p1.ambient_dimension != l2.p1.ambient_dimension:
1082 if isinstance(l1, Line2D):
1083 l1, l2 = l2, l1
1084 _, p1 = Point._normalize_dimension(l1.p1, l2.p1, on_morph='ignore')
1085 _, p2 = Point._normalize_dimension(l1.p2, l2.p2, on_morph='ignore')
1086 l2 = Line(p1, p2)
1088 point = intersection(l1, l2)
1090 # Three cases: Lines may intersect in a point, may be equal or may not intersect.
1091 if not point:
1092 raise GeometryError("The lines do not intersect")
1093 else:
1094 pt = point[0]
1095 if isinstance(pt, Line):
1096 # Intersection is a line because both lines are coincident
1097 return [self]
1100 d1 = l1.direction.unit
1101 d2 = l2.direction.unit
1103 bis1 = Line(pt, pt + d1 + d2)
1104 bis2 = Line(pt, pt + d1 - d2)
1106 return [bis1, bis2]
1109class Line(LinearEntity):
1110 """An infinite line in space.
1112 A 2D line is declared with two distinct points, point and slope, or
1113 an equation. A 3D line may be defined with a point and a direction ratio.
1115 Parameters
1116 ==========
1118 p1 : Point
1119 p2 : Point
1120 slope : SymPy expression
1121 direction_ratio : list
1122 equation : equation of a line
1124 Notes
1125 =====
1127 `Line` will automatically subclass to `Line2D` or `Line3D` based
1128 on the dimension of `p1`. The `slope` argument is only relevant
1129 for `Line2D` and the `direction_ratio` argument is only relevant
1130 for `Line3D`.
1132 The order of the points will define the direction of the line
1133 which is used when calculating the angle between lines.
1135 See Also
1136 ========
1138 sympy.geometry.point.Point
1139 sympy.geometry.line.Line2D
1140 sympy.geometry.line.Line3D
1142 Examples
1143 ========
1145 >>> from sympy import Line, Segment, Point, Eq
1146 >>> from sympy.abc import x, y, a, b
1148 >>> L = Line(Point(2,3), Point(3,5))
1149 >>> L
1150 Line2D(Point2D(2, 3), Point2D(3, 5))
1151 >>> L.points
1152 (Point2D(2, 3), Point2D(3, 5))
1153 >>> L.equation()
1154 -2*x + y + 1
1155 >>> L.coefficients
1156 (-2, 1, 1)
1158 Instantiate with keyword ``slope``:
1160 >>> Line(Point(0, 0), slope=0)
1161 Line2D(Point2D(0, 0), Point2D(1, 0))
1163 Instantiate with another linear object
1165 >>> s = Segment((0, 0), (0, 1))
1166 >>> Line(s).equation()
1167 x
1169 The line corresponding to an equation in the for `ax + by + c = 0`,
1170 can be entered:
1172 >>> Line(3*x + y + 18)
1173 Line2D(Point2D(0, -18), Point2D(1, -21))
1175 If `x` or `y` has a different name, then they can be specified, too,
1176 as a string (to match the name) or symbol:
1178 >>> Line(Eq(3*a + b, -18), x='a', y=b)
1179 Line2D(Point2D(0, -18), Point2D(1, -21))
1180 """
1181 def __new__(cls, *args, **kwargs):
1182 if len(args) == 1 and isinstance(args[0], (Expr, Eq)):
1183 missing = uniquely_named_symbol('?', args)
1184 if not kwargs:
1185 x = 'x'
1186 y = 'y'
1187 else:
1188 x = kwargs.pop('x', missing)
1189 y = kwargs.pop('y', missing)
1190 if kwargs:
1191 raise ValueError('expecting only x and y as keywords')
1193 equation = args[0]
1194 if isinstance(equation, Eq):
1195 equation = equation.lhs - equation.rhs
1197 def find_or_missing(x):
1198 try:
1199 return find(x, equation)
1200 except ValueError:
1201 return missing
1202 x = find_or_missing(x)
1203 y = find_or_missing(y)
1205 a, b, c = linear_coeffs(equation, x, y)
1207 if b:
1208 return Line((0, -c/b), slope=-a/b)
1209 if a:
1210 return Line((-c/a, 0), slope=oo)
1212 raise ValueError('not found in equation: %s' % (set('xy') - {x, y}))
1214 else:
1215 if len(args) > 0:
1216 p1 = args[0]
1217 if len(args) > 1:
1218 p2 = args[1]
1219 else:
1220 p2 = None
1222 if isinstance(p1, LinearEntity):
1223 if p2:
1224 raise ValueError('If p1 is a LinearEntity, p2 must be None.')
1225 dim = len(p1.p1)
1226 else:
1227 p1 = Point(p1)
1228 dim = len(p1)
1229 if p2 is not None or isinstance(p2, Point) and p2.ambient_dimension != dim:
1230 p2 = Point(p2)
1232 if dim == 2:
1233 return Line2D(p1, p2, **kwargs)
1234 elif dim == 3:
1235 return Line3D(p1, p2, **kwargs)
1236 return LinearEntity.__new__(cls, p1, p2, **kwargs)
1238 def contains(self, other):
1239 """
1240 Return True if `other` is on this Line, or False otherwise.
1242 Examples
1243 ========
1245 >>> from sympy import Line,Point
1246 >>> p1, p2 = Point(0, 1), Point(3, 4)
1247 >>> l = Line(p1, p2)
1248 >>> l.contains(p1)
1249 True
1250 >>> l.contains((0, 1))
1251 True
1252 >>> l.contains((0, 0))
1253 False
1254 >>> a = (0, 0, 0)
1255 >>> b = (1, 1, 1)
1256 >>> c = (2, 2, 2)
1257 >>> l1 = Line(a, b)
1258 >>> l2 = Line(b, a)
1259 >>> l1 == l2
1260 False
1261 >>> l1 in l2
1262 True
1264 """
1265 if not isinstance(other, GeometryEntity):
1266 other = Point(other, dim=self.ambient_dimension)
1267 if isinstance(other, Point):
1268 return Point.is_collinear(other, self.p1, self.p2)
1269 if isinstance(other, LinearEntity):
1270 return Point.is_collinear(self.p1, self.p2, other.p1, other.p2)
1271 return False
1273 def distance(self, other):
1274 """
1275 Finds the shortest distance between a line and a point.
1277 Raises
1278 ======
1280 NotImplementedError is raised if `other` is not a Point
1282 Examples
1283 ========
1285 >>> from sympy import Point, Line
1286 >>> p1, p2 = Point(0, 0), Point(1, 1)
1287 >>> s = Line(p1, p2)
1288 >>> s.distance(Point(-1, 1))
1289 sqrt(2)
1290 >>> s.distance((-1, 2))
1291 3*sqrt(2)/2
1292 >>> p1, p2 = Point(0, 0, 0), Point(1, 1, 1)
1293 >>> s = Line(p1, p2)
1294 >>> s.distance(Point(-1, 1, 1))
1295 2*sqrt(6)/3
1296 >>> s.distance((-1, 1, 1))
1297 2*sqrt(6)/3
1299 """
1300 if not isinstance(other, GeometryEntity):
1301 other = Point(other, dim=self.ambient_dimension)
1302 if self.contains(other):
1303 return S.Zero
1304 return self.perpendicular_segment(other).length
1306 def equals(self, other):
1307 """Returns True if self and other are the same mathematical entities"""
1308 if not isinstance(other, Line):
1309 return False
1310 return Point.is_collinear(self.p1, other.p1, self.p2, other.p2)
1312 def plot_interval(self, parameter='t'):
1313 """The plot interval for the default geometric plot of line. Gives
1314 values that will produce a line that is +/- 5 units long (where a
1315 unit is the distance between the two points that define the line).
1317 Parameters
1318 ==========
1320 parameter : str, optional
1321 Default value is 't'.
1323 Returns
1324 =======
1326 plot_interval : list (plot interval)
1327 [parameter, lower_bound, upper_bound]
1329 Examples
1330 ========
1332 >>> from sympy import Point, Line
1333 >>> p1, p2 = Point(0, 0), Point(5, 3)
1334 >>> l1 = Line(p1, p2)
1335 >>> l1.plot_interval()
1336 [t, -5, 5]
1338 """
1339 t = _symbol(parameter, real=True)
1340 return [t, -5, 5]
1343class Ray(LinearEntity):
1344 """A Ray is a semi-line in the space with a source point and a direction.
1346 Parameters
1347 ==========
1349 p1 : Point
1350 The source of the Ray
1351 p2 : Point or radian value
1352 This point determines the direction in which the Ray propagates.
1353 If given as an angle it is interpreted in radians with the positive
1354 direction being ccw.
1356 Attributes
1357 ==========
1359 source
1361 See Also
1362 ========
1364 sympy.geometry.line.Ray2D
1365 sympy.geometry.line.Ray3D
1366 sympy.geometry.point.Point
1367 sympy.geometry.line.Line
1369 Notes
1370 =====
1372 `Ray` will automatically subclass to `Ray2D` or `Ray3D` based on the
1373 dimension of `p1`.
1375 Examples
1376 ========
1378 >>> from sympy import Ray, Point, pi
1379 >>> r = Ray(Point(2, 3), Point(3, 5))
1380 >>> r
1381 Ray2D(Point2D(2, 3), Point2D(3, 5))
1382 >>> r.points
1383 (Point2D(2, 3), Point2D(3, 5))
1384 >>> r.source
1385 Point2D(2, 3)
1386 >>> r.xdirection
1387 oo
1388 >>> r.ydirection
1389 oo
1390 >>> r.slope
1391 2
1392 >>> Ray(Point(0, 0), angle=pi/4).slope
1393 1
1395 """
1396 def __new__(cls, p1, p2=None, **kwargs):
1397 p1 = Point(p1)
1398 if p2 is not None:
1399 p1, p2 = Point._normalize_dimension(p1, Point(p2))
1400 dim = len(p1)
1402 if dim == 2:
1403 return Ray2D(p1, p2, **kwargs)
1404 elif dim == 3:
1405 return Ray3D(p1, p2, **kwargs)
1406 return LinearEntity.__new__(cls, p1, p2, **kwargs)
1408 def _svg(self, scale_factor=1., fill_color="#66cc99"):
1409 """Returns SVG path element for the LinearEntity.
1411 Parameters
1412 ==========
1414 scale_factor : float
1415 Multiplication factor for the SVG stroke-width. Default is 1.
1416 fill_color : str, optional
1417 Hex string for fill color. Default is "#66cc99".
1418 """
1419 verts = (N(self.p1), N(self.p2))
1420 coords = ["{},{}".format(p.x, p.y) for p in verts]
1421 path = "M {} L {}".format(coords[0], " L ".join(coords[1:]))
1423 return (
1424 '<path fill-rule="evenodd" fill="{2}" stroke="#555555" '
1425 'stroke-width="{0}" opacity="0.6" d="{1}" '
1426 'marker-start="url(#markerCircle)" marker-end="url(#markerArrow)"/>'
1427 ).format(2.*scale_factor, path, fill_color)
1429 def contains(self, other):
1430 """
1431 Is other GeometryEntity contained in this Ray?
1433 Examples
1434 ========
1436 >>> from sympy import Ray,Point,Segment
1437 >>> p1, p2 = Point(0, 0), Point(4, 4)
1438 >>> r = Ray(p1, p2)
1439 >>> r.contains(p1)
1440 True
1441 >>> r.contains((1, 1))
1442 True
1443 >>> r.contains((1, 3))
1444 False
1445 >>> s = Segment((1, 1), (2, 2))
1446 >>> r.contains(s)
1447 True
1448 >>> s = Segment((1, 2), (2, 5))
1449 >>> r.contains(s)
1450 False
1451 >>> r1 = Ray((2, 2), (3, 3))
1452 >>> r.contains(r1)
1453 True
1454 >>> r1 = Ray((2, 2), (3, 5))
1455 >>> r.contains(r1)
1456 False
1457 """
1458 if not isinstance(other, GeometryEntity):
1459 other = Point(other, dim=self.ambient_dimension)
1460 if isinstance(other, Point):
1461 if Point.is_collinear(self.p1, self.p2, other):
1462 # if we're in the direction of the ray, our
1463 # direction vector dot the ray's direction vector
1464 # should be non-negative
1465 return bool((self.p2 - self.p1).dot(other - self.p1) >= S.Zero)
1466 return False
1467 elif isinstance(other, Ray):
1468 if Point.is_collinear(self.p1, self.p2, other.p1, other.p2):
1469 return bool((self.p2 - self.p1).dot(other.p2 - other.p1) > S.Zero)
1470 return False
1471 elif isinstance(other, Segment):
1472 return other.p1 in self and other.p2 in self
1474 # No other known entity can be contained in a Ray
1475 return False
1477 def distance(self, other):
1478 """
1479 Finds the shortest distance between the ray and a point.
1481 Raises
1482 ======
1484 NotImplementedError is raised if `other` is not a Point
1486 Examples
1487 ========
1489 >>> from sympy import Point, Ray
1490 >>> p1, p2 = Point(0, 0), Point(1, 1)
1491 >>> s = Ray(p1, p2)
1492 >>> s.distance(Point(-1, -1))
1493 sqrt(2)
1494 >>> s.distance((-1, 2))
1495 3*sqrt(2)/2
1496 >>> p1, p2 = Point(0, 0, 0), Point(1, 1, 2)
1497 >>> s = Ray(p1, p2)
1498 >>> s
1499 Ray3D(Point3D(0, 0, 0), Point3D(1, 1, 2))
1500 >>> s.distance(Point(-1, -1, 2))
1501 4*sqrt(3)/3
1502 >>> s.distance((-1, -1, 2))
1503 4*sqrt(3)/3
1505 """
1506 if not isinstance(other, GeometryEntity):
1507 other = Point(other, dim=self.ambient_dimension)
1508 if self.contains(other):
1509 return S.Zero
1511 proj = Line(self.p1, self.p2).projection(other)
1512 if self.contains(proj):
1513 return abs(other - proj)
1514 else:
1515 return abs(other - self.source)
1517 def equals(self, other):
1518 """Returns True if self and other are the same mathematical entities"""
1519 if not isinstance(other, Ray):
1520 return False
1521 return self.source == other.source and other.p2 in self
1523 def plot_interval(self, parameter='t'):
1524 """The plot interval for the default geometric plot of the Ray. Gives
1525 values that will produce a ray that is 10 units long (where a unit is
1526 the distance between the two points that define the ray).
1528 Parameters
1529 ==========
1531 parameter : str, optional
1532 Default value is 't'.
1534 Returns
1535 =======
1537 plot_interval : list
1538 [parameter, lower_bound, upper_bound]
1540 Examples
1541 ========
1543 >>> from sympy import Ray, pi
1544 >>> r = Ray((0, 0), angle=pi/4)
1545 >>> r.plot_interval()
1546 [t, 0, 10]
1548 """
1549 t = _symbol(parameter, real=True)
1550 return [t, 0, 10]
1552 @property
1553 def source(self):
1554 """The point from which the ray emanates.
1556 See Also
1557 ========
1559 sympy.geometry.point.Point
1561 Examples
1562 ========
1564 >>> from sympy import Point, Ray
1565 >>> p1, p2 = Point(0, 0), Point(4, 1)
1566 >>> r1 = Ray(p1, p2)
1567 >>> r1.source
1568 Point2D(0, 0)
1569 >>> p1, p2 = Point(0, 0, 0), Point(4, 1, 5)
1570 >>> r1 = Ray(p2, p1)
1571 >>> r1.source
1572 Point3D(4, 1, 5)
1574 """
1575 return self.p1
1578class Segment(LinearEntity):
1579 """A line segment in space.
1581 Parameters
1582 ==========
1584 p1 : Point
1585 p2 : Point
1587 Attributes
1588 ==========
1590 length : number or SymPy expression
1591 midpoint : Point
1593 See Also
1594 ========
1596 sympy.geometry.line.Segment2D
1597 sympy.geometry.line.Segment3D
1598 sympy.geometry.point.Point
1599 sympy.geometry.line.Line
1601 Notes
1602 =====
1604 If 2D or 3D points are used to define `Segment`, it will
1605 be automatically subclassed to `Segment2D` or `Segment3D`.
1607 Examples
1608 ========
1610 >>> from sympy import Point, Segment
1611 >>> Segment((1, 0), (1, 1)) # tuples are interpreted as pts
1612 Segment2D(Point2D(1, 0), Point2D(1, 1))
1613 >>> s = Segment(Point(4, 3), Point(1, 1))
1614 >>> s.points
1615 (Point2D(4, 3), Point2D(1, 1))
1616 >>> s.slope
1617 2/3
1618 >>> s.length
1619 sqrt(13)
1620 >>> s.midpoint
1621 Point2D(5/2, 2)
1622 >>> Segment((1, 0, 0), (1, 1, 1)) # tuples are interpreted as pts
1623 Segment3D(Point3D(1, 0, 0), Point3D(1, 1, 1))
1624 >>> s = Segment(Point(4, 3, 9), Point(1, 1, 7)); s
1625 Segment3D(Point3D(4, 3, 9), Point3D(1, 1, 7))
1626 >>> s.points
1627 (Point3D(4, 3, 9), Point3D(1, 1, 7))
1628 >>> s.length
1629 sqrt(17)
1630 >>> s.midpoint
1631 Point3D(5/2, 2, 8)
1633 """
1634 def __new__(cls, p1, p2, **kwargs):
1635 p1, p2 = Point._normalize_dimension(Point(p1), Point(p2))
1636 dim = len(p1)
1638 if dim == 2:
1639 return Segment2D(p1, p2, **kwargs)
1640 elif dim == 3:
1641 return Segment3D(p1, p2, **kwargs)
1642 return LinearEntity.__new__(cls, p1, p2, **kwargs)
1644 def contains(self, other):
1645 """
1646 Is the other GeometryEntity contained within this Segment?
1648 Examples
1649 ========
1651 >>> from sympy import Point, Segment
1652 >>> p1, p2 = Point(0, 1), Point(3, 4)
1653 >>> s = Segment(p1, p2)
1654 >>> s2 = Segment(p2, p1)
1655 >>> s.contains(s2)
1656 True
1657 >>> from sympy import Point3D, Segment3D
1658 >>> p1, p2 = Point3D(0, 1, 1), Point3D(3, 4, 5)
1659 >>> s = Segment3D(p1, p2)
1660 >>> s2 = Segment3D(p2, p1)
1661 >>> s.contains(s2)
1662 True
1663 >>> s.contains((p1 + p2)/2)
1664 True
1665 """
1666 if not isinstance(other, GeometryEntity):
1667 other = Point(other, dim=self.ambient_dimension)
1668 if isinstance(other, Point):
1669 if Point.is_collinear(other, self.p1, self.p2):
1670 if isinstance(self, Segment2D):
1671 # if it is collinear and is in the bounding box of the
1672 # segment then it must be on the segment
1673 vert = (1/self.slope).equals(0)
1674 if vert is False:
1675 isin = (self.p1.x - other.x)*(self.p2.x - other.x) <= 0
1676 if isin in (True, False):
1677 return isin
1678 if vert is True:
1679 isin = (self.p1.y - other.y)*(self.p2.y - other.y) <= 0
1680 if isin in (True, False):
1681 return isin
1682 # use the triangle inequality
1683 d1, d2 = other - self.p1, other - self.p2
1684 d = self.p2 - self.p1
1685 # without the call to simplify, SymPy cannot tell that an expression
1686 # like (a+b)*(a/2+b/2) is always non-negative. If it cannot be
1687 # determined, raise an Undecidable error
1688 try:
1689 # the triangle inequality says that |d1|+|d2| >= |d| and is strict
1690 # only if other lies in the line segment
1691 return bool(simplify(Eq(abs(d1) + abs(d2) - abs(d), 0)))
1692 except TypeError:
1693 raise Undecidable("Cannot determine if {} is in {}".format(other, self))
1694 if isinstance(other, Segment):
1695 return other.p1 in self and other.p2 in self
1697 return False
1699 def equals(self, other):
1700 """Returns True if self and other are the same mathematical entities"""
1701 return isinstance(other, self.func) and list(
1702 ordered(self.args)) == list(ordered(other.args))
1704 def distance(self, other):
1705 """
1706 Finds the shortest distance between a line segment and a point.
1708 Raises
1709 ======
1711 NotImplementedError is raised if `other` is not a Point
1713 Examples
1714 ========
1716 >>> from sympy import Point, Segment
1717 >>> p1, p2 = Point(0, 1), Point(3, 4)
1718 >>> s = Segment(p1, p2)
1719 >>> s.distance(Point(10, 15))
1720 sqrt(170)
1721 >>> s.distance((0, 12))
1722 sqrt(73)
1723 >>> from sympy import Point3D, Segment3D
1724 >>> p1, p2 = Point3D(0, 0, 3), Point3D(1, 1, 4)
1725 >>> s = Segment3D(p1, p2)
1726 >>> s.distance(Point3D(10, 15, 12))
1727 sqrt(341)
1728 >>> s.distance((10, 15, 12))
1729 sqrt(341)
1730 """
1731 if not isinstance(other, GeometryEntity):
1732 other = Point(other, dim=self.ambient_dimension)
1733 if isinstance(other, Point):
1734 vp1 = other - self.p1
1735 vp2 = other - self.p2
1737 dot_prod_sign_1 = self.direction.dot(vp1) >= 0
1738 dot_prod_sign_2 = self.direction.dot(vp2) <= 0
1739 if dot_prod_sign_1 and dot_prod_sign_2:
1740 return Line(self.p1, self.p2).distance(other)
1741 if dot_prod_sign_1 and not dot_prod_sign_2:
1742 return abs(vp2)
1743 if not dot_prod_sign_1 and dot_prod_sign_2:
1744 return abs(vp1)
1745 raise NotImplementedError()
1747 @property
1748 def length(self):
1749 """The length of the line segment.
1751 See Also
1752 ========
1754 sympy.geometry.point.Point.distance
1756 Examples
1757 ========
1759 >>> from sympy import Point, Segment
1760 >>> p1, p2 = Point(0, 0), Point(4, 3)
1761 >>> s1 = Segment(p1, p2)
1762 >>> s1.length
1763 5
1764 >>> from sympy import Point3D, Segment3D
1765 >>> p1, p2 = Point3D(0, 0, 0), Point3D(4, 3, 3)
1766 >>> s1 = Segment3D(p1, p2)
1767 >>> s1.length
1768 sqrt(34)
1770 """
1771 return Point.distance(self.p1, self.p2)
1773 @property
1774 def midpoint(self):
1775 """The midpoint of the line segment.
1777 See Also
1778 ========
1780 sympy.geometry.point.Point.midpoint
1782 Examples
1783 ========
1785 >>> from sympy import Point, Segment
1786 >>> p1, p2 = Point(0, 0), Point(4, 3)
1787 >>> s1 = Segment(p1, p2)
1788 >>> s1.midpoint
1789 Point2D(2, 3/2)
1790 >>> from sympy import Point3D, Segment3D
1791 >>> p1, p2 = Point3D(0, 0, 0), Point3D(4, 3, 3)
1792 >>> s1 = Segment3D(p1, p2)
1793 >>> s1.midpoint
1794 Point3D(2, 3/2, 3/2)
1796 """
1797 return Point.midpoint(self.p1, self.p2)
1799 def perpendicular_bisector(self, p=None):
1800 """The perpendicular bisector of this segment.
1802 If no point is specified or the point specified is not on the
1803 bisector then the bisector is returned as a Line. Otherwise a
1804 Segment is returned that joins the point specified and the
1805 intersection of the bisector and the segment.
1807 Parameters
1808 ==========
1810 p : Point
1812 Returns
1813 =======
1815 bisector : Line or Segment
1817 See Also
1818 ========
1820 LinearEntity.perpendicular_segment
1822 Examples
1823 ========
1825 >>> from sympy import Point, Segment
1826 >>> p1, p2, p3 = Point(0, 0), Point(6, 6), Point(5, 1)
1827 >>> s1 = Segment(p1, p2)
1828 >>> s1.perpendicular_bisector()
1829 Line2D(Point2D(3, 3), Point2D(-3, 9))
1831 >>> s1.perpendicular_bisector(p3)
1832 Segment2D(Point2D(5, 1), Point2D(3, 3))
1834 """
1835 l = self.perpendicular_line(self.midpoint)
1836 if p is not None:
1837 p2 = Point(p, dim=self.ambient_dimension)
1838 if p2 in l:
1839 return Segment(p2, self.midpoint)
1840 return l
1842 def plot_interval(self, parameter='t'):
1843 """The plot interval for the default geometric plot of the Segment gives
1844 values that will produce the full segment in a plot.
1846 Parameters
1847 ==========
1849 parameter : str, optional
1850 Default value is 't'.
1852 Returns
1853 =======
1855 plot_interval : list
1856 [parameter, lower_bound, upper_bound]
1858 Examples
1859 ========
1861 >>> from sympy import Point, Segment
1862 >>> p1, p2 = Point(0, 0), Point(5, 3)
1863 >>> s1 = Segment(p1, p2)
1864 >>> s1.plot_interval()
1865 [t, 0, 1]
1867 """
1868 t = _symbol(parameter, real=True)
1869 return [t, 0, 1]
1872class LinearEntity2D(LinearEntity):
1873 """A base class for all linear entities (line, ray and segment)
1874 in a 2-dimensional Euclidean space.
1876 Attributes
1877 ==========
1879 p1
1880 p2
1881 coefficients
1882 slope
1883 points
1885 Notes
1886 =====
1888 This is an abstract class and is not meant to be instantiated.
1890 See Also
1891 ========
1893 sympy.geometry.entity.GeometryEntity
1895 """
1896 @property
1897 def bounds(self):
1898 """Return a tuple (xmin, ymin, xmax, ymax) representing the bounding
1899 rectangle for the geometric figure.
1901 """
1902 verts = self.points
1903 xs = [p.x for p in verts]
1904 ys = [p.y for p in verts]
1905 return (min(xs), min(ys), max(xs), max(ys))
1907 def perpendicular_line(self, p):
1908 """Create a new Line perpendicular to this linear entity which passes
1909 through the point `p`.
1911 Parameters
1912 ==========
1914 p : Point
1916 Returns
1917 =======
1919 line : Line
1921 See Also
1922 ========
1924 sympy.geometry.line.LinearEntity.is_perpendicular, perpendicular_segment
1926 Examples
1927 ========
1929 >>> from sympy import Point, Line
1930 >>> p1, p2, p3 = Point(0, 0), Point(2, 3), Point(-2, 2)
1931 >>> L = Line(p1, p2)
1932 >>> P = L.perpendicular_line(p3); P
1933 Line2D(Point2D(-2, 2), Point2D(-5, 4))
1934 >>> L.is_perpendicular(P)
1935 True
1937 In 2D, the first point of the perpendicular line is the
1938 point through which was required to pass; the second
1939 point is arbitrarily chosen. To get a line that explicitly
1940 uses a point in the line, create a line from the perpendicular
1941 segment from the line to the point:
1943 >>> Line(L.perpendicular_segment(p3))
1944 Line2D(Point2D(-2, 2), Point2D(4/13, 6/13))
1945 """
1946 p = Point(p, dim=self.ambient_dimension)
1947 # any two lines in R^2 intersect, so blindly making
1948 # a line through p in an orthogonal direction will work
1949 # and is faster than finding the projection point as in 3D
1950 return Line(p, p + self.direction.orthogonal_direction)
1952 @property
1953 def slope(self):
1954 """The slope of this linear entity, or infinity if vertical.
1956 Returns
1957 =======
1959 slope : number or SymPy expression
1961 See Also
1962 ========
1964 coefficients
1966 Examples
1967 ========
1969 >>> from sympy import Point, Line
1970 >>> p1, p2 = Point(0, 0), Point(3, 5)
1971 >>> l1 = Line(p1, p2)
1972 >>> l1.slope
1973 5/3
1975 >>> p3 = Point(0, 4)
1976 >>> l2 = Line(p1, p3)
1977 >>> l2.slope
1978 oo
1980 """
1981 d1, d2 = (self.p1 - self.p2).args
1982 if d1 == 0:
1983 return S.Infinity
1984 return simplify(d2/d1)
1987class Line2D(LinearEntity2D, Line):
1988 """An infinite line in space 2D.
1990 A line is declared with two distinct points or a point and slope
1991 as defined using keyword `slope`.
1993 Parameters
1994 ==========
1996 p1 : Point
1997 pt : Point
1998 slope : SymPy expression
2000 See Also
2001 ========
2003 sympy.geometry.point.Point
2005 Examples
2006 ========
2008 >>> from sympy import Line, Segment, Point
2009 >>> L = Line(Point(2,3), Point(3,5))
2010 >>> L
2011 Line2D(Point2D(2, 3), Point2D(3, 5))
2012 >>> L.points
2013 (Point2D(2, 3), Point2D(3, 5))
2014 >>> L.equation()
2015 -2*x + y + 1
2016 >>> L.coefficients
2017 (-2, 1, 1)
2019 Instantiate with keyword ``slope``:
2021 >>> Line(Point(0, 0), slope=0)
2022 Line2D(Point2D(0, 0), Point2D(1, 0))
2024 Instantiate with another linear object
2026 >>> s = Segment((0, 0), (0, 1))
2027 >>> Line(s).equation()
2028 x
2029 """
2030 def __new__(cls, p1, pt=None, slope=None, **kwargs):
2031 if isinstance(p1, LinearEntity):
2032 if pt is not None:
2033 raise ValueError('When p1 is a LinearEntity, pt should be None')
2034 p1, pt = Point._normalize_dimension(*p1.args, dim=2)
2035 else:
2036 p1 = Point(p1, dim=2)
2037 if pt is not None and slope is None:
2038 try:
2039 p2 = Point(pt, dim=2)
2040 except (NotImplementedError, TypeError, ValueError):
2041 raise ValueError(filldedent('''
2042 The 2nd argument was not a valid Point.
2043 If it was a slope, enter it with keyword "slope".
2044 '''))
2045 elif slope is not None and pt is None:
2046 slope = sympify(slope)
2047 if slope.is_finite is False:
2048 # when infinite slope, don't change x
2049 dx = 0
2050 dy = 1
2051 else:
2052 # go over 1 up slope
2053 dx = 1
2054 dy = slope
2055 # XXX avoiding simplification by adding to coords directly
2056 p2 = Point(p1.x + dx, p1.y + dy, evaluate=False)
2057 else:
2058 raise ValueError('A 2nd Point or keyword "slope" must be used.')
2059 return LinearEntity2D.__new__(cls, p1, p2, **kwargs)
2061 def _svg(self, scale_factor=1., fill_color="#66cc99"):
2062 """Returns SVG path element for the LinearEntity.
2064 Parameters
2065 ==========
2067 scale_factor : float
2068 Multiplication factor for the SVG stroke-width. Default is 1.
2069 fill_color : str, optional
2070 Hex string for fill color. Default is "#66cc99".
2071 """
2072 verts = (N(self.p1), N(self.p2))
2073 coords = ["{},{}".format(p.x, p.y) for p in verts]
2074 path = "M {} L {}".format(coords[0], " L ".join(coords[1:]))
2076 return (
2077 '<path fill-rule="evenodd" fill="{2}" stroke="#555555" '
2078 'stroke-width="{0}" opacity="0.6" d="{1}" '
2079 'marker-start="url(#markerReverseArrow)" marker-end="url(#markerArrow)"/>'
2080 ).format(2.*scale_factor, path, fill_color)
2082 @property
2083 def coefficients(self):
2084 """The coefficients (`a`, `b`, `c`) for `ax + by + c = 0`.
2086 See Also
2087 ========
2089 sympy.geometry.line.Line2D.equation
2091 Examples
2092 ========
2094 >>> from sympy import Point, Line
2095 >>> from sympy.abc import x, y
2096 >>> p1, p2 = Point(0, 0), Point(5, 3)
2097 >>> l = Line(p1, p2)
2098 >>> l.coefficients
2099 (-3, 5, 0)
2101 >>> p3 = Point(x, y)
2102 >>> l2 = Line(p1, p3)
2103 >>> l2.coefficients
2104 (-y, x, 0)
2106 """
2107 p1, p2 = self.points
2108 if p1.x == p2.x:
2109 return (S.One, S.Zero, -p1.x)
2110 elif p1.y == p2.y:
2111 return (S.Zero, S.One, -p1.y)
2112 return tuple([simplify(i) for i in
2113 (self.p1.y - self.p2.y,
2114 self.p2.x - self.p1.x,
2115 self.p1.x*self.p2.y - self.p1.y*self.p2.x)])
2117 def equation(self, x='x', y='y'):
2118 """The equation of the line: ax + by + c.
2120 Parameters
2121 ==========
2123 x : str, optional
2124 The name to use for the x-axis, default value is 'x'.
2125 y : str, optional
2126 The name to use for the y-axis, default value is 'y'.
2128 Returns
2129 =======
2131 equation : SymPy expression
2133 See Also
2134 ========
2136 sympy.geometry.line.Line2D.coefficients
2138 Examples
2139 ========
2141 >>> from sympy import Point, Line
2142 >>> p1, p2 = Point(1, 0), Point(5, 3)
2143 >>> l1 = Line(p1, p2)
2144 >>> l1.equation()
2145 -3*x + 4*y + 3
2147 """
2148 x = _symbol(x, real=True)
2149 y = _symbol(y, real=True)
2150 p1, p2 = self.points
2151 if p1.x == p2.x:
2152 return x - p1.x
2153 elif p1.y == p2.y:
2154 return y - p1.y
2156 a, b, c = self.coefficients
2157 return a*x + b*y + c
2160class Ray2D(LinearEntity2D, Ray):
2161 """
2162 A Ray is a semi-line in the space with a source point and a direction.
2164 Parameters
2165 ==========
2167 p1 : Point
2168 The source of the Ray
2169 p2 : Point or radian value
2170 This point determines the direction in which the Ray propagates.
2171 If given as an angle it is interpreted in radians with the positive
2172 direction being ccw.
2174 Attributes
2175 ==========
2177 source
2178 xdirection
2179 ydirection
2181 See Also
2182 ========
2184 sympy.geometry.point.Point, Line
2186 Examples
2187 ========
2189 >>> from sympy import Point, pi, Ray
2190 >>> r = Ray(Point(2, 3), Point(3, 5))
2191 >>> r
2192 Ray2D(Point2D(2, 3), Point2D(3, 5))
2193 >>> r.points
2194 (Point2D(2, 3), Point2D(3, 5))
2195 >>> r.source
2196 Point2D(2, 3)
2197 >>> r.xdirection
2198 oo
2199 >>> r.ydirection
2200 oo
2201 >>> r.slope
2202 2
2203 >>> Ray(Point(0, 0), angle=pi/4).slope
2204 1
2206 """
2207 def __new__(cls, p1, pt=None, angle=None, **kwargs):
2208 p1 = Point(p1, dim=2)
2209 if pt is not None and angle is None:
2210 try:
2211 p2 = Point(pt, dim=2)
2212 except (NotImplementedError, TypeError, ValueError):
2213 raise ValueError(filldedent('''
2214 The 2nd argument was not a valid Point; if
2215 it was meant to be an angle it should be
2216 given with keyword "angle".'''))
2217 if p1 == p2:
2218 raise ValueError('A Ray requires two distinct points.')
2219 elif angle is not None and pt is None:
2220 # we need to know if the angle is an odd multiple of pi/2
2221 angle = sympify(angle)
2222 c = _pi_coeff(angle)
2223 p2 = None
2224 if c is not None:
2225 if c.is_Rational:
2226 if c.q == 2:
2227 if c.p == 1:
2228 p2 = p1 + Point(0, 1)
2229 elif c.p == 3:
2230 p2 = p1 + Point(0, -1)
2231 elif c.q == 1:
2232 if c.p == 0:
2233 p2 = p1 + Point(1, 0)
2234 elif c.p == 1:
2235 p2 = p1 + Point(-1, 0)
2236 if p2 is None:
2237 c *= S.Pi
2238 else:
2239 c = angle % (2*S.Pi)
2240 if not p2:
2241 m = 2*c/S.Pi
2242 left = And(1 < m, m < 3) # is it in quadrant 2 or 3?
2243 x = Piecewise((-1, left), (Piecewise((0, Eq(m % 1, 0)), (1, True)), True))
2244 y = Piecewise((-tan(c), left), (Piecewise((1, Eq(m, 1)), (-1, Eq(m, 3)), (tan(c), True)), True))
2245 p2 = p1 + Point(x, y)
2246 else:
2247 raise ValueError('A 2nd point or keyword "angle" must be used.')
2249 return LinearEntity2D.__new__(cls, p1, p2, **kwargs)
2251 @property
2252 def xdirection(self):
2253 """The x direction of the ray.
2255 Positive infinity if the ray points in the positive x direction,
2256 negative infinity if the ray points in the negative x direction,
2257 or 0 if the ray is vertical.
2259 See Also
2260 ========
2262 ydirection
2264 Examples
2265 ========
2267 >>> from sympy import Point, Ray
2268 >>> p1, p2, p3 = Point(0, 0), Point(1, 1), Point(0, -1)
2269 >>> r1, r2 = Ray(p1, p2), Ray(p1, p3)
2270 >>> r1.xdirection
2271 oo
2272 >>> r2.xdirection
2273 0
2275 """
2276 if self.p1.x < self.p2.x:
2277 return S.Infinity
2278 elif self.p1.x == self.p2.x:
2279 return S.Zero
2280 else:
2281 return S.NegativeInfinity
2283 @property
2284 def ydirection(self):
2285 """The y direction of the ray.
2287 Positive infinity if the ray points in the positive y direction,
2288 negative infinity if the ray points in the negative y direction,
2289 or 0 if the ray is horizontal.
2291 See Also
2292 ========
2294 xdirection
2296 Examples
2297 ========
2299 >>> from sympy import Point, Ray
2300 >>> p1, p2, p3 = Point(0, 0), Point(-1, -1), Point(-1, 0)
2301 >>> r1, r2 = Ray(p1, p2), Ray(p1, p3)
2302 >>> r1.ydirection
2303 -oo
2304 >>> r2.ydirection
2305 0
2307 """
2308 if self.p1.y < self.p2.y:
2309 return S.Infinity
2310 elif self.p1.y == self.p2.y:
2311 return S.Zero
2312 else:
2313 return S.NegativeInfinity
2315 def closing_angle(r1, r2):
2316 """Return the angle by which r2 must be rotated so it faces the same
2317 direction as r1.
2319 Parameters
2320 ==========
2322 r1 : Ray2D
2323 r2 : Ray2D
2325 Returns
2326 =======
2328 angle : angle in radians (ccw angle is positive)
2330 See Also
2331 ========
2333 LinearEntity.angle_between
2335 Examples
2336 ========
2338 >>> from sympy import Ray, pi
2339 >>> r1 = Ray((0, 0), (1, 0))
2340 >>> r2 = r1.rotate(-pi/2)
2341 >>> angle = r1.closing_angle(r2); angle
2342 pi/2
2343 >>> r2.rotate(angle).direction.unit == r1.direction.unit
2344 True
2345 >>> r2.closing_angle(r1)
2346 -pi/2
2347 """
2348 if not all(isinstance(r, Ray2D) for r in (r1, r2)):
2349 # although the direction property is defined for
2350 # all linear entities, only the Ray is truly a
2351 # directed object
2352 raise TypeError('Both arguments must be Ray2D objects.')
2354 a1 = atan2(*list(reversed(r1.direction.args)))
2355 a2 = atan2(*list(reversed(r2.direction.args)))
2356 if a1*a2 < 0:
2357 a1 = 2*S.Pi + a1 if a1 < 0 else a1
2358 a2 = 2*S.Pi + a2 if a2 < 0 else a2
2359 return a1 - a2
2362class Segment2D(LinearEntity2D, Segment):
2363 """A line segment in 2D space.
2365 Parameters
2366 ==========
2368 p1 : Point
2369 p2 : Point
2371 Attributes
2372 ==========
2374 length : number or SymPy expression
2375 midpoint : Point
2377 See Also
2378 ========
2380 sympy.geometry.point.Point, Line
2382 Examples
2383 ========
2385 >>> from sympy import Point, Segment
2386 >>> Segment((1, 0), (1, 1)) # tuples are interpreted as pts
2387 Segment2D(Point2D(1, 0), Point2D(1, 1))
2388 >>> s = Segment(Point(4, 3), Point(1, 1)); s
2389 Segment2D(Point2D(4, 3), Point2D(1, 1))
2390 >>> s.points
2391 (Point2D(4, 3), Point2D(1, 1))
2392 >>> s.slope
2393 2/3
2394 >>> s.length
2395 sqrt(13)
2396 >>> s.midpoint
2397 Point2D(5/2, 2)
2399 """
2400 def __new__(cls, p1, p2, **kwargs):
2401 p1 = Point(p1, dim=2)
2402 p2 = Point(p2, dim=2)
2404 if p1 == p2:
2405 return p1
2407 return LinearEntity2D.__new__(cls, p1, p2, **kwargs)
2409 def _svg(self, scale_factor=1., fill_color="#66cc99"):
2410 """Returns SVG path element for the LinearEntity.
2412 Parameters
2413 ==========
2415 scale_factor : float
2416 Multiplication factor for the SVG stroke-width. Default is 1.
2417 fill_color : str, optional
2418 Hex string for fill color. Default is "#66cc99".
2419 """
2420 verts = (N(self.p1), N(self.p2))
2421 coords = ["{},{}".format(p.x, p.y) for p in verts]
2422 path = "M {} L {}".format(coords[0], " L ".join(coords[1:]))
2423 return (
2424 '<path fill-rule="evenodd" fill="{2}" stroke="#555555" '
2425 'stroke-width="{0}" opacity="0.6" d="{1}" />'
2426 ).format(2.*scale_factor, path, fill_color)
2429class LinearEntity3D(LinearEntity):
2430 """An base class for all linear entities (line, ray and segment)
2431 in a 3-dimensional Euclidean space.
2433 Attributes
2434 ==========
2436 p1
2437 p2
2438 direction_ratio
2439 direction_cosine
2440 points
2442 Notes
2443 =====
2445 This is a base class and is not meant to be instantiated.
2446 """
2447 def __new__(cls, p1, p2, **kwargs):
2448 p1 = Point3D(p1, dim=3)
2449 p2 = Point3D(p2, dim=3)
2450 if p1 == p2:
2451 # if it makes sense to return a Point, handle in subclass
2452 raise ValueError(
2453 "%s.__new__ requires two unique Points." % cls.__name__)
2455 return GeometryEntity.__new__(cls, p1, p2, **kwargs)
2457 ambient_dimension = 3
2459 @property
2460 def direction_ratio(self):
2461 """The direction ratio of a given line in 3D.
2463 See Also
2464 ========
2466 sympy.geometry.line.Line3D.equation
2468 Examples
2469 ========
2471 >>> from sympy import Point3D, Line3D
2472 >>> p1, p2 = Point3D(0, 0, 0), Point3D(5, 3, 1)
2473 >>> l = Line3D(p1, p2)
2474 >>> l.direction_ratio
2475 [5, 3, 1]
2476 """
2477 p1, p2 = self.points
2478 return p1.direction_ratio(p2)
2480 @property
2481 def direction_cosine(self):
2482 """The normalized direction ratio of a given line in 3D.
2484 See Also
2485 ========
2487 sympy.geometry.line.Line3D.equation
2489 Examples
2490 ========
2492 >>> from sympy import Point3D, Line3D
2493 >>> p1, p2 = Point3D(0, 0, 0), Point3D(5, 3, 1)
2494 >>> l = Line3D(p1, p2)
2495 >>> l.direction_cosine
2496 [sqrt(35)/7, 3*sqrt(35)/35, sqrt(35)/35]
2497 >>> sum(i**2 for i in _)
2498 1
2499 """
2500 p1, p2 = self.points
2501 return p1.direction_cosine(p2)
2504class Line3D(LinearEntity3D, Line):
2505 """An infinite 3D line in space.
2507 A line is declared with two distinct points or a point and direction_ratio
2508 as defined using keyword `direction_ratio`.
2510 Parameters
2511 ==========
2513 p1 : Point3D
2514 pt : Point3D
2515 direction_ratio : list
2517 See Also
2518 ========
2520 sympy.geometry.point.Point3D
2521 sympy.geometry.line.Line
2522 sympy.geometry.line.Line2D
2524 Examples
2525 ========
2527 >>> from sympy import Line3D, Point3D
2528 >>> L = Line3D(Point3D(2, 3, 4), Point3D(3, 5, 1))
2529 >>> L
2530 Line3D(Point3D(2, 3, 4), Point3D(3, 5, 1))
2531 >>> L.points
2532 (Point3D(2, 3, 4), Point3D(3, 5, 1))
2533 """
2534 def __new__(cls, p1, pt=None, direction_ratio=(), **kwargs):
2535 if isinstance(p1, LinearEntity3D):
2536 if pt is not None:
2537 raise ValueError('if p1 is a LinearEntity, pt must be None.')
2538 p1, pt = p1.args
2539 else:
2540 p1 = Point(p1, dim=3)
2541 if pt is not None and len(direction_ratio) == 0:
2542 pt = Point(pt, dim=3)
2543 elif len(direction_ratio) == 3 and pt is None:
2544 pt = Point3D(p1.x + direction_ratio[0], p1.y + direction_ratio[1],
2545 p1.z + direction_ratio[2])
2546 else:
2547 raise ValueError('A 2nd Point or keyword "direction_ratio" must '
2548 'be used.')
2550 return LinearEntity3D.__new__(cls, p1, pt, **kwargs)
2552 def equation(self, x='x', y='y', z='z'):
2553 """Return the equations that define the line in 3D.
2555 Parameters
2556 ==========
2558 x : str, optional
2559 The name to use for the x-axis, default value is 'x'.
2560 y : str, optional
2561 The name to use for the y-axis, default value is 'y'.
2562 z : str, optional
2563 The name to use for the z-axis, default value is 'z'.
2565 Returns
2566 =======
2568 equation : Tuple of simultaneous equations
2570 Examples
2571 ========
2573 >>> from sympy import Point3D, Line3D, solve
2574 >>> from sympy.abc import x, y, z
2575 >>> p1, p2 = Point3D(1, 0, 0), Point3D(5, 3, 0)
2576 >>> l1 = Line3D(p1, p2)
2577 >>> eq = l1.equation(x, y, z); eq
2578 (-3*x + 4*y + 3, z)
2579 >>> solve(eq.subs(z, 0), (x, y, z))
2580 {x: 4*y/3 + 1}
2581 """
2582 x, y, z, k = [_symbol(i, real=True) for i in (x, y, z, 'k')]
2583 p1, p2 = self.points
2584 d1, d2, d3 = p1.direction_ratio(p2)
2585 x1, y1, z1 = p1
2586 eqs = [-d1*k + x - x1, -d2*k + y - y1, -d3*k + z - z1]
2587 # eliminate k from equations by solving first eq with k for k
2588 for i, e in enumerate(eqs):
2589 if e.has(k):
2590 kk = solve(eqs[i], k)[0]
2591 eqs.pop(i)
2592 break
2593 return Tuple(*[i.subs(k, kk).as_numer_denom()[0] for i in eqs])
2596class Ray3D(LinearEntity3D, Ray):
2597 """
2598 A Ray is a semi-line in the space with a source point and a direction.
2600 Parameters
2601 ==========
2603 p1 : Point3D
2604 The source of the Ray
2605 p2 : Point or a direction vector
2606 direction_ratio: Determines the direction in which the Ray propagates.
2609 Attributes
2610 ==========
2612 source
2613 xdirection
2614 ydirection
2615 zdirection
2617 See Also
2618 ========
2620 sympy.geometry.point.Point3D, Line3D
2623 Examples
2624 ========
2626 >>> from sympy import Point3D, Ray3D
2627 >>> r = Ray3D(Point3D(2, 3, 4), Point3D(3, 5, 0))
2628 >>> r
2629 Ray3D(Point3D(2, 3, 4), Point3D(3, 5, 0))
2630 >>> r.points
2631 (Point3D(2, 3, 4), Point3D(3, 5, 0))
2632 >>> r.source
2633 Point3D(2, 3, 4)
2634 >>> r.xdirection
2635 oo
2636 >>> r.ydirection
2637 oo
2638 >>> r.direction_ratio
2639 [1, 2, -4]
2641 """
2642 def __new__(cls, p1, pt=None, direction_ratio=(), **kwargs):
2643 if isinstance(p1, LinearEntity3D):
2644 if pt is not None:
2645 raise ValueError('If p1 is a LinearEntity, pt must be None')
2646 p1, pt = p1.args
2647 else:
2648 p1 = Point(p1, dim=3)
2649 if pt is not None and len(direction_ratio) == 0:
2650 pt = Point(pt, dim=3)
2651 elif len(direction_ratio) == 3 and pt is None:
2652 pt = Point3D(p1.x + direction_ratio[0], p1.y + direction_ratio[1],
2653 p1.z + direction_ratio[2])
2654 else:
2655 raise ValueError(filldedent('''
2656 A 2nd Point or keyword "direction_ratio" must be used.
2657 '''))
2659 return LinearEntity3D.__new__(cls, p1, pt, **kwargs)
2661 @property
2662 def xdirection(self):
2663 """The x direction of the ray.
2665 Positive infinity if the ray points in the positive x direction,
2666 negative infinity if the ray points in the negative x direction,
2667 or 0 if the ray is vertical.
2669 See Also
2670 ========
2672 ydirection
2674 Examples
2675 ========
2677 >>> from sympy import Point3D, Ray3D
2678 >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(1, 1, 1), Point3D(0, -1, 0)
2679 >>> r1, r2 = Ray3D(p1, p2), Ray3D(p1, p3)
2680 >>> r1.xdirection
2681 oo
2682 >>> r2.xdirection
2683 0
2685 """
2686 if self.p1.x < self.p2.x:
2687 return S.Infinity
2688 elif self.p1.x == self.p2.x:
2689 return S.Zero
2690 else:
2691 return S.NegativeInfinity
2693 @property
2694 def ydirection(self):
2695 """The y direction of the ray.
2697 Positive infinity if the ray points in the positive y direction,
2698 negative infinity if the ray points in the negative y direction,
2699 or 0 if the ray is horizontal.
2701 See Also
2702 ========
2704 xdirection
2706 Examples
2707 ========
2709 >>> from sympy import Point3D, Ray3D
2710 >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(-1, -1, -1), Point3D(-1, 0, 0)
2711 >>> r1, r2 = Ray3D(p1, p2), Ray3D(p1, p3)
2712 >>> r1.ydirection
2713 -oo
2714 >>> r2.ydirection
2715 0
2717 """
2718 if self.p1.y < self.p2.y:
2719 return S.Infinity
2720 elif self.p1.y == self.p2.y:
2721 return S.Zero
2722 else:
2723 return S.NegativeInfinity
2725 @property
2726 def zdirection(self):
2727 """The z direction of the ray.
2729 Positive infinity if the ray points in the positive z direction,
2730 negative infinity if the ray points in the negative z direction,
2731 or 0 if the ray is horizontal.
2733 See Also
2734 ========
2736 xdirection
2738 Examples
2739 ========
2741 >>> from sympy import Point3D, Ray3D
2742 >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(-1, -1, -1), Point3D(-1, 0, 0)
2743 >>> r1, r2 = Ray3D(p1, p2), Ray3D(p1, p3)
2744 >>> r1.ydirection
2745 -oo
2746 >>> r2.ydirection
2747 0
2748 >>> r2.zdirection
2749 0
2751 """
2752 if self.p1.z < self.p2.z:
2753 return S.Infinity
2754 elif self.p1.z == self.p2.z:
2755 return S.Zero
2756 else:
2757 return S.NegativeInfinity
2760class Segment3D(LinearEntity3D, Segment):
2761 """A line segment in a 3D space.
2763 Parameters
2764 ==========
2766 p1 : Point3D
2767 p2 : Point3D
2769 Attributes
2770 ==========
2772 length : number or SymPy expression
2773 midpoint : Point3D
2775 See Also
2776 ========
2778 sympy.geometry.point.Point3D, Line3D
2780 Examples
2781 ========
2783 >>> from sympy import Point3D, Segment3D
2784 >>> Segment3D((1, 0, 0), (1, 1, 1)) # tuples are interpreted as pts
2785 Segment3D(Point3D(1, 0, 0), Point3D(1, 1, 1))
2786 >>> s = Segment3D(Point3D(4, 3, 9), Point3D(1, 1, 7)); s
2787 Segment3D(Point3D(4, 3, 9), Point3D(1, 1, 7))
2788 >>> s.points
2789 (Point3D(4, 3, 9), Point3D(1, 1, 7))
2790 >>> s.length
2791 sqrt(17)
2792 >>> s.midpoint
2793 Point3D(5/2, 2, 8)
2795 """
2796 def __new__(cls, p1, p2, **kwargs):
2797 p1 = Point(p1, dim=3)
2798 p2 = Point(p2, dim=3)
2800 if p1 == p2:
2801 return p1
2803 return LinearEntity3D.__new__(cls, p1, p2, **kwargs)