Added a warning when constructing a Matrix without bracket + test modified
[sympy.git] / sympy / geometry / line.py
blob704bc4b122a95a351e610776f90fd7fcb5614ae2
1 from sympy.core.basic import Basic, S, C
2 from sympy.simplify import simplify
3 from sympy.geometry.exceptions import GeometryError
4 from entity import GeometryEntity
5 from point import Point
7 class LinearEntity(GeometryEntity):
8 """
9 A linear entity (line, ray, segment, etc) in space.
11 This is an abstract class and is not meant to be instantiated.
12 Subclasses should implement the following methods:
13 __eq__
14 __contains__
15 """
16 def __new__(cls, p1, p2, **kwargs):
17 if not isinstance(p1, Point) or not isinstance(p2, Point):
18 raise TypeError("%s.__new__ requires Point instances" % cls.__name__)
19 if p1 == p2:
20 raise RuntimeError("%s.__new__ requires two distinct points" % cls.__name__)
22 return GeometryEntity.__new__(cls, p1, p2, **kwargs)
24 @property
25 def p1(self):
26 """One of the defining points of a linear entity."""
27 return self.__getitem__(0)
29 @property
30 def p2(self):
31 """One of the defining points of a linear entity."""
32 return self.__getitem__(1)
34 @property
35 def coefficients(self):
36 """The coefficients (a,b,c) for equation ax+by+c=0"""
37 return (self.p1[1]-self.p2[1],
38 self.p2[0]-self.p1[0],
39 self.p1[0]*self.p2[1] - self.p1[1]*self.p2[0])
41 def is_concurrent(*lines):
42 """
43 Returns True if the set of linear entities are concurrent, False
44 otherwise. Two or more linear entities are concurrent if they all
45 intersect at a single point.
47 Description of Method Used:
48 ===========================
49 Simply take the first two lines and find their intersection.
50 If there is no intersection, then the first two lines were
51 parallel and had no intersection so concurrenecy is impossible
52 amongst the whole set. Otherwise, check to see if the
53 intersection point of the first two lines is a member on
54 the rest of the lines. If so, the lines are concurrent.
55 """
56 _lines = lines
57 lines = GeometryEntity.extract_entities(lines)
59 # Concurrency requires intersection at a single point; One linear
60 # entity cannot be concurrent.
61 if len(lines) <= 1:
62 return False
64 try:
65 # Get the intersection (if parallel)
66 p = GeometryEntity.do_intersection(lines[0], lines[1])
67 if len(p) == 0: return False
69 # Make sure the intersection is on every linear entity
70 for line in lines[2:]:
71 if p[0] not in line:
72 return False
73 return True
74 except AttributeError:
75 return False
77 def is_parallel(l1, l2):
78 """Returns True if l1 and l2 are parallel, False otherwise"""
79 try:
80 a1,b1,c1 = l1.coefficients
81 a2,b2,c2 = l2.coefficients
82 return bool(simplify(a1*b2 - b1*a2) == 0)
83 except AttributeError:
84 return False
86 def is_perpendicular(l1, l2):
87 """Returns True if l1 and l2 are perpendicular, False otherwise"""
88 try:
89 a1,b1,c1 = l1.coefficients
90 a2,b2,c2 = l2.coefficients
91 return bool(simplify(a1*a2 + b1*b2) == 0)
92 except AttributeError:
93 return False
95 def angle_between(l1, l2):
96 """
97 Returns an angle formed between the two linear entities.
99 Description of Method Used:
100 ===========================
101 From the dot product of vectors v1 and v2 it is known that:
102 dot(v1, v2) = |v1|*|v2|*cos(A)
103 where A is the angle formed between the two vectors. We can
104 get the directional vectors of the two lines and readily
105 find the angle between the two using the above formula.
107 v1 = l1.p2 - l1.p1
108 v2 = l2.p2 - l2.p1
109 return C.acos( (v1[0]*v2[0]+v1[1]*v2[1]) / (abs(v1)*abs(v2)) )
111 def parallel_line(self, p):
113 Returns a new Line which is parallel to this linear entity and passes
114 through the specified point.
116 d = self.p1 - self.p2
117 return Line(p, p + d)
119 def perpendicular_line(self, p):
121 Returns a new Line which is perpendicular to this linear entity and
122 passes through the specified point.
124 d1,d2 = self.p1 - self.p2
125 if d2 == 0: # If an horizontal line
126 if p[1] == self.p1[1]: # if p is on this linear entity
127 p2 = Point(p[0], p[1] + 1)
128 return Line(p, p2)
129 else:
130 p2 = Point(p[0], self.p1[1])
131 return Line(p, p2)
132 else:
133 p2 = Point(p[0] - d2, p[1] + d1)
134 return Line(p, p2)
136 def perpendicular_segment(self, p):
138 Returns a new Segment which connects p to a point on this linear
139 entity and is also perpendicular to this line. Returns p itself
140 if p is on this linear entity.
142 if p in self:
143 return p
144 pl = self.perpendicular_line(p)
145 p2 = GeometryEntity.do_intersection(self, pl)[0]
146 return Segment(p, p2)
148 @property
149 def slope(self):
151 The slope of this linear entity, or infinity if vertical.
153 d1,d2 = self.p1 - self.p2
154 if d1 == 0:
155 return S.Infinity
156 return simplify(d2/d1)
158 @property
159 def points(self):
160 """The two points used to define this linear entity."""
161 return (self.p1, self.p2)
163 def projection(self, o):
165 Project a point, line, ray, or segment onto this linear entity.
166 If projection cannot be performed then a GeometryError is raised.
168 Notes:
169 ======
170 - A projection involves taking the two points that define
171 the linear entity and projecting those points onto a
172 Line and then reforming the linear entity using these
173 projections.
174 - A point P is projected onto a line L by finding the point
175 on L that is closest to P. This is done by creating a
176 perpendicular line through P and L and finding its
177 intersection with L.
179 tline = Line(self.p1, self.p2)
181 def project(p):
182 """Project a point onto the line representing self."""
183 if p in tline: return p
184 l1 = tline.perpendicular_line(p)
185 return tline.intersection(l1)[0]
187 projected = None
188 if isinstance(o, Point):
189 return project(o)
190 elif isinstance(o, LinearEntity):
191 n_p1 = project(o.p1)
192 n_p2 = project(o.p2)
193 if n_p1 == n_p2:
194 projected = n_p1
195 else:
196 projected = o.__class__(n_p1, n_p2)
198 # Didn't know how to project so raise an error
199 if projected is None:
200 n1 = self.__class__.__name__
201 n2 = o.__class__.__name__
202 raise GeometryError("Do not know how to project %s onto %s" % (n2, n1))
204 return GeometryEntity.do_intersection(self, projected)[0]
206 def intersection(self, o):
207 if isinstance(o, Point):
208 if o in self:
209 return [o]
210 else:
211 return []
212 elif isinstance(o, LinearEntity):
213 a1,b1,c1 = self.coefficients
214 a2,b2,c2 = o.coefficients
215 t = simplify(a1*b2 - a2*b1)
216 if t == 0: # are parallel?
217 if isinstance(self, Line):
218 if o.p1 in self:
219 return [o]
220 return []
221 elif isinstance(o, Line):
222 if self.p1 in o:
223 return [self]
224 return []
225 elif isinstance(self, Ray):
226 if isinstance(o, Ray):
227 # case 1, rays in the same direction
228 if self.xdirection == o.xdirection:
229 if self.source[0] < o.source[0]:
230 return [o]
231 return [self]
232 # case 2, rays in the opposite directions
233 else:
234 if o.source in self:
235 if self.source == o.source:
236 return [self.source]
237 return [Segment(o.source, self.source)]
238 return []
239 elif isinstance(o, Segment):
240 if o.p1 in self:
241 if o.p2 in self:
242 return [o]
243 return [Segment(o.p1, self.source)]
244 elif o.p2 in self:
245 return [Segment(o.p2, self.source)]
246 return []
247 elif isinstance(self, Segment):
248 if isinstance(o, Ray):
249 return o.intersection(self)
250 elif isinstance(o, Segment):
251 # A reminder that the points of Segments are ordered
252 # in such a way that the following works. See
253 # Segment.__new__ for details on the ordering.
254 if self.p1 not in o:
255 if self.p2 not in o:
256 # Neither of the endpoints are in o so either
257 # o is contained in this segment or it isn't
258 if o in self:
259 return [self]
260 return []
261 else:
262 # p1 not in o but p2 is. Either there is a
263 # segment as an intersection, or they only
264 # intersect at an endpoint
265 if self.p2 == o.p1:
266 return [o.p1]
267 return [Segment(o.p1, self.p2)]
268 elif self.p2 not in o:
269 # p2 not in o but p1 is. Either there is a
270 # segment as an intersection, or they only
271 # intersect at an endpoint
272 if self.p1 == o.p2:
273 return [o.p2]
274 return [Segment(o.p2, self.p1)]
276 # Both points of self in o so the whole segment
277 # is in o
278 return [self]
280 # Unknown linear entity
281 return []
283 # Not parallel, so find the point of intersection
284 px = simplify((b1*c2 - c1*b2) / t)
285 py = simplify((a2*c1 - a1*c2) / t)
286 inter = Point(px, py)
287 if inter in self and inter in o:
288 return [inter]
289 return []
290 raise NotImplementedError()
292 def random_point(self):
293 """Returns a random point on this Ray."""
294 from random import randint
295 from sys import maxint
297 # The lower and upper
298 lower, upper = -maxint-1, maxint
300 if self.slope is S.Infinity:
301 if isinstance(self, Ray):
302 if self.ydirection is S.Infinity:
303 lower = self.p1[1]
304 else:
305 upper = self.p1[1]
306 elif isinstance(self, Segment):
307 lower = self.p1[1]
308 upper = self.p2[1]
310 x = self.p1[0]
311 y = randint(lower, upper)
312 else:
313 if isinstance(self, Ray):
314 if self.xdirection is S.Infinity:
315 lower = self.p1[0]
316 else:
317 upper = self.p1[0]
318 elif isinstance(self, Segment):
319 lower = self.p1[0]
320 upper = self.p2[0]
322 a,b,c = self.coefficients
323 x = randint(lower, upper)
324 y = simplify( (-c - a*x) / b )
325 return Point(x, y)
327 def __eq__(self, other):
328 raise NotImplementedError()
330 def __contains__(self, other):
331 raise NotImplementedError()
334 class Line(LinearEntity):
335 """A line in space."""
337 def arbitrary_point(self, parameter_name='t'):
338 """Returns a symbolic point that is on this line."""
339 t = C.Symbol(parameter_name, real=True)
340 x = simplify(self.p1[0] + t*(self.p2[0] - self.p1[0]))
341 y = simplify(self.p1[1] + t*(self.p2[1] - self.p1[1]))
342 return Point(x, y)
344 def plot_interval(self, parameter_name='t'):
345 """Returns the plot interval for the default geometric plot of line"""
346 t = C.Symbol(parameter_name, real=True)
347 return [t, -5, 5]
349 def equation(self, xaxis_name='x', yaxis_name='y'):
351 Returns the equation for this line. Optional parameters xaxis_name
352 and yaxis_name can be used to specify the names of the symbols used
353 for the equation.
355 x = C.Symbol(xaxis_name, real=True)
356 y = C.Symbol(yaxis_name, real=True)
357 a,b,c = self.coefficients
358 return simplify(a*x + b*y + c)
360 def __contains__(self, o):
361 """Return True if o is on this Line, or False otherwise."""
362 if isinstance(o, Line):
363 return self.__eq__(o)
364 elif isinstance(o, Point):
365 x = C.Symbol('x', real=True)
366 y = C.Symbol('y', real=True)
367 r = self.equation().subs({x: o[0], y: o[1]})
368 x = simplify(r)
369 return simplify(x) == 0
370 else:
371 return False
373 def __eq__(self, other):
374 """Return True if other is equal to this Line, or False otherwise."""
375 if not isinstance(other, Line): return False
376 return Point.is_collinear(self.p1, self.p2, other.p1, other.p2)
379 class Ray(LinearEntity):
380 """A ray in space."""
382 @property
383 def source(self):
384 """The point from which the ray eminates."""
385 return self.p1
387 @property
388 def xdirection(self):
390 The x direction of the ray. Positive infinity if the ray points in
391 the positive x direction, negative infinity if the ray points
392 in the negative x direction, or 0 if the ray is vertical.
394 if self.p1[0] < self.p2[0]:
395 return S.Infinity
396 elif self.p1[0] == self.p2[0]:
397 return S.Zero
398 else:
399 return S.NegativeInfinity
401 @property
402 def ydirection(self):
404 The y direction of the ray. Positive infinity if the ray points in
405 the positive y direction, negative infinity if the ray points
406 in the negative y direction, or 0 if the ray is horizontal.
408 if self.p1[1] < self.p2[1]:
409 return S.Infinity
410 elif self.p1[1] == self.p2[1]:
411 return S.Zero
412 else:
413 return S.NegativeInfinity
415 def __eq__(self, other):
416 """Return True if other is equal to this Ray, or False otherwise."""
417 if not isinstance(other, Ray):
418 return False
419 return ((self.source == other.source) and (other.p2 in self))
421 def __contains__(self, o):
422 """Return True if o is on this Ray, or False otherwise."""
423 if isinstance(o, Ray):
424 d = o.p2 - o.p1
425 return Point.is_collinear(self.p1, self.p2, o.p1, o.p2) \
426 and (self.xdirection == o.xdirection) \
427 and (self.ydirection == o.ydirection)
428 elif isinstance(o, Segment):
429 return ((o.p1 in self) and (o.p2 in self))
430 elif isinstance(o, Point):
431 if Point.is_collinear(self.p1, self.p2, o):
432 if (not self.p1[0].atoms(C.Symbol)) and (not self.p1[1].atoms(C.Symbol)) \
433 and (not self.p2[0].atoms(C.Symbol)) and (not self.p2[1].atoms(C.Symbol)):
434 if self.xdirection is S.Infinity:
435 return o[0] >= self.source[0]
436 elif self.xdirection is S.NegativeInfinity:
437 return o[0] <= self.source[0]
438 elif self.ydirection is S.Infinity:
439 return o[1] >= self.source[1]
440 return o[1] <= self.source[1]
441 else:
442 # There are symbols lying around, so assume that o
443 # is contained in this ray (for now)
444 return True
445 else:
446 # Points are not collinear, so the rays are not parallel
447 # and hence it isimpossible for self to contain o
448 return False
450 # No other known entity can be contained in a Ray
451 return False
454 class Segment(LinearEntity):
455 """An undirected line segment in space."""
457 def __new__(cls, p1, p2, **kwargs):
458 # Reorder the two points under the following ordering:
459 # if p1[0] != p2[0] then p1[0] < p2[0]
460 # if p1[0] == p2[0] then p1[1] < p2[1]
461 if p1[0] > p2[0]:
462 p1, p2 = p2, p1
463 elif p1[0] == p2[0] and p1[1] > p2[0]:
464 p1, p2 = p2, p1
465 return LinearEntity.__new__(cls, p1, p2, **kwargs)
467 def arbitrary_point(self, parameter_name='t'):
468 """Returns a symbolic point that is on this line segment."""
469 t = C.Symbol(parameter_name, real=True)
470 x = simplify(self.p1[0] + t*(self.p2[0] - self.p1[0]))
471 y = simplify(self.p1[1] + t*(self.p2[1] - self.p1[1]))
472 return Point(x, y)
474 def plot_interval(self, parameter_name='t'):
475 t = C.Symbol(parameter_name, real=True)
476 return [t, 0, 1]
478 def perpendicular_bisector(self, p=None):
480 Returns the perpendicular bisector of this segment. If no point is
481 specified or the point specified is not on the bisector then the
482 bisector is returned as a Line. Otherwise a Segment is returned
483 that joins the point specified and the intersection of the bisector
484 and the segment.
486 l = LinearEntity.perpendicular_line(self, self.midpoint)
487 if p is None or p not in l:
488 return l
489 else:
490 return Segment(self.midpoint, p)
492 @property
493 def length(self):
494 """The length of the segment."""
495 return Point.distance(self.p1, self.p2)
497 @property
498 def midpoint(self):
499 """The midpoint of the segment."""
500 return Point.midpoint(self.p1, self.p2)
502 def __eq__(self, other):
503 """Return True if other is equal to this Line, or False otherwise."""
504 if not isinstance(other, Segment):
505 return False
506 return ((self.p1 == other.p1) and (self.p2 == other.p2))
508 def __contains__(self, o):
509 """Return True if o is on this Segment, or False otherwise."""
510 if isinstance(o, Segment):
511 return ((o.p1 in self) and (o.p2 in self))
512 elif isinstance(o, Point):
513 if Point.is_collinear(self.p1, self.p2, o):
514 x1,x2 = self.p1[0], self.p2[0]
515 if not (x1.atoms(C.Symbol)) or (x2.atoms(C.Symbol)):
516 return (min(x1,x2) <= o[0]) and (o[0] <= max(x1,x2))
517 else:
518 return True
519 else:
520 return False
522 # No other known entity can be contained in a Ray
523 return False