Coverage for /usr/lib/python3/dist-packages/sympy/geometry/plane.py: 14%
293 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"""Geometrical Planes.
3Contains
4========
5Plane
7"""
9from sympy.core import Dummy, Rational, S, Symbol
10from sympy.core.symbol import _symbol
11from sympy.functions.elementary.trigonometric import cos, sin, acos, asin, sqrt
12from .entity import GeometryEntity
13from .line import (Line, Ray, Segment, Line3D, LinearEntity, LinearEntity3D,
14 Ray3D, Segment3D)
15from .point import Point, Point3D
16from sympy.matrices import Matrix
17from sympy.polys.polytools import cancel
18from sympy.solvers import solve, linsolve
19from sympy.utilities.iterables import uniq, is_sequence
20from sympy.utilities.misc import filldedent, func_name, Undecidable
22from mpmath.libmp.libmpf import prec_to_dps
24import random
27x, y, z, t = [Dummy('plane_dummy') for i in range(4)]
30class Plane(GeometryEntity):
31 """
32 A plane is a flat, two-dimensional surface. A plane is the two-dimensional
33 analogue of a point (zero-dimensions), a line (one-dimension) and a solid
34 (three-dimensions). A plane can generally be constructed by two types of
35 inputs. They are three non-collinear points and a point and the plane's
36 normal vector.
38 Attributes
39 ==========
41 p1
42 normal_vector
44 Examples
45 ========
47 >>> from sympy import Plane, Point3D
48 >>> Plane(Point3D(1, 1, 1), Point3D(2, 3, 4), Point3D(2, 2, 2))
49 Plane(Point3D(1, 1, 1), (-1, 2, -1))
50 >>> Plane((1, 1, 1), (2, 3, 4), (2, 2, 2))
51 Plane(Point3D(1, 1, 1), (-1, 2, -1))
52 >>> Plane(Point3D(1, 1, 1), normal_vector=(1,4,7))
53 Plane(Point3D(1, 1, 1), (1, 4, 7))
55 """
56 def __new__(cls, p1, a=None, b=None, **kwargs):
57 p1 = Point3D(p1, dim=3)
58 if a and b:
59 p2 = Point(a, dim=3)
60 p3 = Point(b, dim=3)
61 if Point3D.are_collinear(p1, p2, p3):
62 raise ValueError('Enter three non-collinear points')
63 a = p1.direction_ratio(p2)
64 b = p1.direction_ratio(p3)
65 normal_vector = tuple(Matrix(a).cross(Matrix(b)))
66 else:
67 a = kwargs.pop('normal_vector', a)
68 evaluate = kwargs.get('evaluate', True)
69 if is_sequence(a) and len(a) == 3:
70 normal_vector = Point3D(a).args if evaluate else a
71 else:
72 raise ValueError(filldedent('''
73 Either provide 3 3D points or a point with a
74 normal vector expressed as a sequence of length 3'''))
75 if all(coord.is_zero for coord in normal_vector):
76 raise ValueError('Normal vector cannot be zero vector')
77 return GeometryEntity.__new__(cls, p1, normal_vector, **kwargs)
79 def __contains__(self, o):
80 k = self.equation(x, y, z)
81 if isinstance(o, (LinearEntity, LinearEntity3D)):
82 d = Point3D(o.arbitrary_point(t))
83 e = k.subs([(x, d.x), (y, d.y), (z, d.z)])
84 return e.equals(0)
85 try:
86 o = Point(o, dim=3, strict=True)
87 d = k.xreplace(dict(zip((x, y, z), o.args)))
88 return d.equals(0)
89 except TypeError:
90 return False
92 def _eval_evalf(self, prec=15, **options):
93 pt, tup = self.args
94 dps = prec_to_dps(prec)
95 pt = pt.evalf(n=dps, **options)
96 tup = tuple([i.evalf(n=dps, **options) for i in tup])
97 return self.func(pt, normal_vector=tup, evaluate=False)
99 def angle_between(self, o):
100 """Angle between the plane and other geometric entity.
102 Parameters
103 ==========
105 LinearEntity3D, Plane.
107 Returns
108 =======
110 angle : angle in radians
112 Notes
113 =====
115 This method accepts only 3D entities as it's parameter, but if you want
116 to calculate the angle between a 2D entity and a plane you should
117 first convert to a 3D entity by projecting onto a desired plane and
118 then proceed to calculate the angle.
120 Examples
121 ========
123 >>> from sympy import Point3D, Line3D, Plane
124 >>> a = Plane(Point3D(1, 2, 2), normal_vector=(1, 2, 3))
125 >>> b = Line3D(Point3D(1, 3, 4), Point3D(2, 2, 2))
126 >>> a.angle_between(b)
127 -asin(sqrt(21)/6)
129 """
130 if isinstance(o, LinearEntity3D):
131 a = Matrix(self.normal_vector)
132 b = Matrix(o.direction_ratio)
133 c = a.dot(b)
134 d = sqrt(sum([i**2 for i in self.normal_vector]))
135 e = sqrt(sum([i**2 for i in o.direction_ratio]))
136 return asin(c/(d*e))
137 if isinstance(o, Plane):
138 a = Matrix(self.normal_vector)
139 b = Matrix(o.normal_vector)
140 c = a.dot(b)
141 d = sqrt(sum([i**2 for i in self.normal_vector]))
142 e = sqrt(sum([i**2 for i in o.normal_vector]))
143 return acos(c/(d*e))
146 def arbitrary_point(self, u=None, v=None):
147 """ Returns an arbitrary point on the Plane. If given two
148 parameters, the point ranges over the entire plane. If given 1
149 or no parameters, returns a point with one parameter which,
150 when varying from 0 to 2*pi, moves the point in a circle of
151 radius 1 about p1 of the Plane.
153 Examples
154 ========
156 >>> from sympy import Plane, Ray
157 >>> from sympy.abc import u, v, t, r
158 >>> p = Plane((1, 1, 1), normal_vector=(1, 0, 0))
159 >>> p.arbitrary_point(u, v)
160 Point3D(1, u + 1, v + 1)
161 >>> p.arbitrary_point(t)
162 Point3D(1, cos(t) + 1, sin(t) + 1)
164 While arbitrary values of u and v can move the point anywhere in
165 the plane, the single-parameter point can be used to construct a
166 ray whose arbitrary point can be located at angle t and radius
167 r from p.p1:
169 >>> Ray(p.p1, _).arbitrary_point(r)
170 Point3D(1, r*cos(t) + 1, r*sin(t) + 1)
172 Returns
173 =======
175 Point3D
177 """
178 circle = v is None
179 if circle:
180 u = _symbol(u or 't', real=True)
181 else:
182 u = _symbol(u or 'u', real=True)
183 v = _symbol(v or 'v', real=True)
184 x, y, z = self.normal_vector
185 a, b, c = self.p1.args
186 # x1, y1, z1 is a nonzero vector parallel to the plane
187 if x.is_zero and y.is_zero:
188 x1, y1, z1 = S.One, S.Zero, S.Zero
189 else:
190 x1, y1, z1 = -y, x, S.Zero
191 # x2, y2, z2 is also parallel to the plane, and orthogonal to x1, y1, z1
192 x2, y2, z2 = tuple(Matrix((x, y, z)).cross(Matrix((x1, y1, z1))))
193 if circle:
194 x1, y1, z1 = (w/sqrt(x1**2 + y1**2 + z1**2) for w in (x1, y1, z1))
195 x2, y2, z2 = (w/sqrt(x2**2 + y2**2 + z2**2) for w in (x2, y2, z2))
196 p = Point3D(a + x1*cos(u) + x2*sin(u), \
197 b + y1*cos(u) + y2*sin(u), \
198 c + z1*cos(u) + z2*sin(u))
199 else:
200 p = Point3D(a + x1*u + x2*v, b + y1*u + y2*v, c + z1*u + z2*v)
201 return p
204 @staticmethod
205 def are_concurrent(*planes):
206 """Is a sequence of Planes concurrent?
208 Two or more Planes are concurrent if their intersections
209 are a common line.
211 Parameters
212 ==========
214 planes: list
216 Returns
217 =======
219 Boolean
221 Examples
222 ========
224 >>> from sympy import Plane, Point3D
225 >>> a = Plane(Point3D(5, 0, 0), normal_vector=(1, -1, 1))
226 >>> b = Plane(Point3D(0, -2, 0), normal_vector=(3, 1, 1))
227 >>> c = Plane(Point3D(0, -1, 0), normal_vector=(5, -1, 9))
228 >>> Plane.are_concurrent(a, b)
229 True
230 >>> Plane.are_concurrent(a, b, c)
231 False
233 """
234 planes = list(uniq(planes))
235 for i in planes:
236 if not isinstance(i, Plane):
237 raise ValueError('All objects should be Planes but got %s' % i.func)
238 if len(planes) < 2:
239 return False
240 planes = list(planes)
241 first = planes.pop(0)
242 sol = first.intersection(planes[0])
243 if sol == []:
244 return False
245 else:
246 line = sol[0]
247 for i in planes[1:]:
248 l = first.intersection(i)
249 if not l or l[0] not in line:
250 return False
251 return True
254 def distance(self, o):
255 """Distance between the plane and another geometric entity.
257 Parameters
258 ==========
260 Point3D, LinearEntity3D, Plane.
262 Returns
263 =======
265 distance
267 Notes
268 =====
270 This method accepts only 3D entities as it's parameter, but if you want
271 to calculate the distance between a 2D entity and a plane you should
272 first convert to a 3D entity by projecting onto a desired plane and
273 then proceed to calculate the distance.
275 Examples
276 ========
278 >>> from sympy import Point3D, Line3D, Plane
279 >>> a = Plane(Point3D(1, 1, 1), normal_vector=(1, 1, 1))
280 >>> b = Point3D(1, 2, 3)
281 >>> a.distance(b)
282 sqrt(3)
283 >>> c = Line3D(Point3D(2, 3, 1), Point3D(1, 2, 2))
284 >>> a.distance(c)
285 0
287 """
288 if self.intersection(o) != []:
289 return S.Zero
291 if isinstance(o, (Segment3D, Ray3D)):
292 a, b = o.p1, o.p2
293 pi, = self.intersection(Line3D(a, b))
294 if pi in o:
295 return self.distance(pi)
296 elif a in Segment3D(pi, b):
297 return self.distance(a)
298 else:
299 assert isinstance(o, Segment3D) is True
300 return self.distance(b)
302 # following code handles `Point3D`, `LinearEntity3D`, `Plane`
303 a = o if isinstance(o, Point3D) else o.p1
304 n = Point3D(self.normal_vector).unit
305 d = (a - self.p1).dot(n)
306 return abs(d)
309 def equals(self, o):
310 """
311 Returns True if self and o are the same mathematical entities.
313 Examples
314 ========
316 >>> from sympy import Plane, Point3D
317 >>> a = Plane(Point3D(1, 2, 3), normal_vector=(1, 1, 1))
318 >>> b = Plane(Point3D(1, 2, 3), normal_vector=(2, 2, 2))
319 >>> c = Plane(Point3D(1, 2, 3), normal_vector=(-1, 4, 6))
320 >>> a.equals(a)
321 True
322 >>> a.equals(b)
323 True
324 >>> a.equals(c)
325 False
326 """
327 if isinstance(o, Plane):
328 a = self.equation()
329 b = o.equation()
330 return cancel(a/b).is_constant()
331 else:
332 return False
335 def equation(self, x=None, y=None, z=None):
336 """The equation of the Plane.
338 Examples
339 ========
341 >>> from sympy import Point3D, Plane
342 >>> a = Plane(Point3D(1, 1, 2), Point3D(2, 4, 7), Point3D(3, 5, 1))
343 >>> a.equation()
344 -23*x + 11*y - 2*z + 16
345 >>> a = Plane(Point3D(1, 4, 2), normal_vector=(6, 6, 6))
346 >>> a.equation()
347 6*x + 6*y + 6*z - 42
349 """
350 x, y, z = [i if i else Symbol(j, real=True) for i, j in zip((x, y, z), 'xyz')]
351 a = Point3D(x, y, z)
352 b = self.p1.direction_ratio(a)
353 c = self.normal_vector
354 return (sum(i*j for i, j in zip(b, c)))
357 def intersection(self, o):
358 """ The intersection with other geometrical entity.
360 Parameters
361 ==========
363 Point, Point3D, LinearEntity, LinearEntity3D, Plane
365 Returns
366 =======
368 List
370 Examples
371 ========
373 >>> from sympy import Point3D, Line3D, Plane
374 >>> a = Plane(Point3D(1, 2, 3), normal_vector=(1, 1, 1))
375 >>> b = Point3D(1, 2, 3)
376 >>> a.intersection(b)
377 [Point3D(1, 2, 3)]
378 >>> c = Line3D(Point3D(1, 4, 7), Point3D(2, 2, 2))
379 >>> a.intersection(c)
380 [Point3D(2, 2, 2)]
381 >>> d = Plane(Point3D(6, 0, 0), normal_vector=(2, -5, 3))
382 >>> e = Plane(Point3D(2, 0, 0), normal_vector=(3, 4, -3))
383 >>> d.intersection(e)
384 [Line3D(Point3D(78/23, -24/23, 0), Point3D(147/23, 321/23, 23))]
386 """
387 if not isinstance(o, GeometryEntity):
388 o = Point(o, dim=3)
389 if isinstance(o, Point):
390 if o in self:
391 return [o]
392 else:
393 return []
394 if isinstance(o, (LinearEntity, LinearEntity3D)):
395 # recast to 3D
396 p1, p2 = o.p1, o.p2
397 if isinstance(o, Segment):
398 o = Segment3D(p1, p2)
399 elif isinstance(o, Ray):
400 o = Ray3D(p1, p2)
401 elif isinstance(o, Line):
402 o = Line3D(p1, p2)
403 else:
404 raise ValueError('unhandled linear entity: %s' % o.func)
405 if o in self:
406 return [o]
407 else:
408 a = Point3D(o.arbitrary_point(t))
409 p1, n = self.p1, Point3D(self.normal_vector)
411 # TODO: Replace solve with solveset, when this line is tested
412 c = solve((a - p1).dot(n), t)
413 if not c:
414 return []
415 else:
416 c = [i for i in c if i.is_real is not False]
417 if len(c) > 1:
418 c = [i for i in c if i.is_real]
419 if len(c) != 1:
420 raise Undecidable("not sure which point is real")
421 p = a.subs(t, c[0])
422 if p not in o:
423 return [] # e.g. a segment might not intersect a plane
424 return [p]
425 if isinstance(o, Plane):
426 if self.equals(o):
427 return [self]
428 if self.is_parallel(o):
429 return []
430 else:
431 x, y, z = map(Dummy, 'xyz')
432 a, b = Matrix([self.normal_vector]), Matrix([o.normal_vector])
433 c = list(a.cross(b))
434 d = self.equation(x, y, z)
435 e = o.equation(x, y, z)
436 result = list(linsolve([d, e], x, y, z))[0]
437 for i in (x, y, z): result = result.subs(i, 0)
438 return [Line3D(Point3D(result), direction_ratio=c)]
441 def is_coplanar(self, o):
442 """ Returns True if `o` is coplanar with self, else False.
444 Examples
445 ========
447 >>> from sympy import Plane
448 >>> o = (0, 0, 0)
449 >>> p = Plane(o, (1, 1, 1))
450 >>> p2 = Plane(o, (2, 2, 2))
451 >>> p == p2
452 False
453 >>> p.is_coplanar(p2)
454 True
455 """
456 if isinstance(o, Plane):
457 return not cancel(self.equation(x, y, z)/o.equation(x, y, z)).has(x, y, z)
458 if isinstance(o, Point3D):
459 return o in self
460 elif isinstance(o, LinearEntity3D):
461 return all(i in self for i in self)
462 elif isinstance(o, GeometryEntity): # XXX should only be handling 2D objects now
463 return all(i == 0 for i in self.normal_vector[:2])
466 def is_parallel(self, l):
467 """Is the given geometric entity parallel to the plane?
469 Parameters
470 ==========
472 LinearEntity3D or Plane
474 Returns
475 =======
477 Boolean
479 Examples
480 ========
482 >>> from sympy import Plane, Point3D
483 >>> a = Plane(Point3D(1,4,6), normal_vector=(2, 4, 6))
484 >>> b = Plane(Point3D(3,1,3), normal_vector=(4, 8, 12))
485 >>> a.is_parallel(b)
486 True
488 """
489 if isinstance(l, LinearEntity3D):
490 a = l.direction_ratio
491 b = self.normal_vector
492 c = sum([i*j for i, j in zip(a, b)])
493 if c == 0:
494 return True
495 else:
496 return False
497 elif isinstance(l, Plane):
498 a = Matrix(l.normal_vector)
499 b = Matrix(self.normal_vector)
500 if a.cross(b).is_zero_matrix:
501 return True
502 else:
503 return False
506 def is_perpendicular(self, l):
507 """Is the given geometric entity perpendicualar to the given plane?
509 Parameters
510 ==========
512 LinearEntity3D or Plane
514 Returns
515 =======
517 Boolean
519 Examples
520 ========
522 >>> from sympy import Plane, Point3D
523 >>> a = Plane(Point3D(1,4,6), normal_vector=(2, 4, 6))
524 >>> b = Plane(Point3D(2, 2, 2), normal_vector=(-1, 2, -1))
525 >>> a.is_perpendicular(b)
526 True
528 """
529 if isinstance(l, LinearEntity3D):
530 a = Matrix(l.direction_ratio)
531 b = Matrix(self.normal_vector)
532 if a.cross(b).is_zero_matrix:
533 return True
534 else:
535 return False
536 elif isinstance(l, Plane):
537 a = Matrix(l.normal_vector)
538 b = Matrix(self.normal_vector)
539 if a.dot(b) == 0:
540 return True
541 else:
542 return False
543 else:
544 return False
546 @property
547 def normal_vector(self):
548 """Normal vector of the given plane.
550 Examples
551 ========
553 >>> from sympy import Point3D, Plane
554 >>> a = Plane(Point3D(1, 1, 1), Point3D(2, 3, 4), Point3D(2, 2, 2))
555 >>> a.normal_vector
556 (-1, 2, -1)
557 >>> a = Plane(Point3D(1, 1, 1), normal_vector=(1, 4, 7))
558 >>> a.normal_vector
559 (1, 4, 7)
561 """
562 return self.args[1]
564 @property
565 def p1(self):
566 """The only defining point of the plane. Others can be obtained from the
567 arbitrary_point method.
569 See Also
570 ========
572 sympy.geometry.point.Point3D
574 Examples
575 ========
577 >>> from sympy import Point3D, Plane
578 >>> a = Plane(Point3D(1, 1, 1), Point3D(2, 3, 4), Point3D(2, 2, 2))
579 >>> a.p1
580 Point3D(1, 1, 1)
582 """
583 return self.args[0]
585 def parallel_plane(self, pt):
586 """
587 Plane parallel to the given plane and passing through the point pt.
589 Parameters
590 ==========
592 pt: Point3D
594 Returns
595 =======
597 Plane
599 Examples
600 ========
602 >>> from sympy import Plane, Point3D
603 >>> a = Plane(Point3D(1, 4, 6), normal_vector=(2, 4, 6))
604 >>> a.parallel_plane(Point3D(2, 3, 5))
605 Plane(Point3D(2, 3, 5), (2, 4, 6))
607 """
608 a = self.normal_vector
609 return Plane(pt, normal_vector=a)
611 def perpendicular_line(self, pt):
612 """A line perpendicular to the given plane.
614 Parameters
615 ==========
617 pt: Point3D
619 Returns
620 =======
622 Line3D
624 Examples
625 ========
627 >>> from sympy import Plane, Point3D
628 >>> a = Plane(Point3D(1,4,6), normal_vector=(2, 4, 6))
629 >>> a.perpendicular_line(Point3D(9, 8, 7))
630 Line3D(Point3D(9, 8, 7), Point3D(11, 12, 13))
632 """
633 a = self.normal_vector
634 return Line3D(pt, direction_ratio=a)
636 def perpendicular_plane(self, *pts):
637 """
638 Return a perpendicular passing through the given points. If the
639 direction ratio between the points is the same as the Plane's normal
640 vector then, to select from the infinite number of possible planes,
641 a third point will be chosen on the z-axis (or the y-axis
642 if the normal vector is already parallel to the z-axis). If less than
643 two points are given they will be supplied as follows: if no point is
644 given then pt1 will be self.p1; if a second point is not given it will
645 be a point through pt1 on a line parallel to the z-axis (if the normal
646 is not already the z-axis, otherwise on the line parallel to the
647 y-axis).
649 Parameters
650 ==========
652 pts: 0, 1 or 2 Point3D
654 Returns
655 =======
657 Plane
659 Examples
660 ========
662 >>> from sympy import Plane, Point3D
663 >>> a, b = Point3D(0, 0, 0), Point3D(0, 1, 0)
664 >>> Z = (0, 0, 1)
665 >>> p = Plane(a, normal_vector=Z)
666 >>> p.perpendicular_plane(a, b)
667 Plane(Point3D(0, 0, 0), (1, 0, 0))
668 """
669 if len(pts) > 2:
670 raise ValueError('No more than 2 pts should be provided.')
672 pts = list(pts)
673 if len(pts) == 0:
674 pts.append(self.p1)
675 if len(pts) == 1:
676 x, y, z = self.normal_vector
677 if x == y == 0:
678 dir = (0, 1, 0)
679 else:
680 dir = (0, 0, 1)
681 pts.append(pts[0] + Point3D(*dir))
683 p1, p2 = [Point(i, dim=3) for i in pts]
684 l = Line3D(p1, p2)
685 n = Line3D(p1, direction_ratio=self.normal_vector)
686 if l in n: # XXX should an error be raised instead?
687 # there are infinitely many perpendicular planes;
688 x, y, z = self.normal_vector
689 if x == y == 0:
690 # the z axis is the normal so pick a pt on the y-axis
691 p3 = Point3D(0, 1, 0) # case 1
692 else:
693 # else pick a pt on the z axis
694 p3 = Point3D(0, 0, 1) # case 2
695 # in case that point is already given, move it a bit
696 if p3 in l:
697 p3 *= 2 # case 3
698 else:
699 p3 = p1 + Point3D(*self.normal_vector) # case 4
700 return Plane(p1, p2, p3)
702 def projection_line(self, line):
703 """Project the given line onto the plane through the normal plane
704 containing the line.
706 Parameters
707 ==========
709 LinearEntity or LinearEntity3D
711 Returns
712 =======
714 Point3D, Line3D, Ray3D or Segment3D
716 Notes
717 =====
719 For the interaction between 2D and 3D lines(segments, rays), you should
720 convert the line to 3D by using this method. For example for finding the
721 intersection between a 2D and a 3D line, convert the 2D line to a 3D line
722 by projecting it on a required plane and then proceed to find the
723 intersection between those lines.
725 Examples
726 ========
728 >>> from sympy import Plane, Line, Line3D, Point3D
729 >>> a = Plane(Point3D(1, 1, 1), normal_vector=(1, 1, 1))
730 >>> b = Line(Point3D(1, 1), Point3D(2, 2))
731 >>> a.projection_line(b)
732 Line3D(Point3D(4/3, 4/3, 1/3), Point3D(5/3, 5/3, -1/3))
733 >>> c = Line3D(Point3D(1, 1, 1), Point3D(2, 2, 2))
734 >>> a.projection_line(c)
735 Point3D(1, 1, 1)
737 """
738 if not isinstance(line, (LinearEntity, LinearEntity3D)):
739 raise NotImplementedError('Enter a linear entity only')
740 a, b = self.projection(line.p1), self.projection(line.p2)
741 if a == b:
742 # projection does not imply intersection so for
743 # this case (line parallel to plane's normal) we
744 # return the projection point
745 return a
746 if isinstance(line, (Line, Line3D)):
747 return Line3D(a, b)
748 if isinstance(line, (Ray, Ray3D)):
749 return Ray3D(a, b)
750 if isinstance(line, (Segment, Segment3D)):
751 return Segment3D(a, b)
753 def projection(self, pt):
754 """Project the given point onto the plane along the plane normal.
756 Parameters
757 ==========
759 Point or Point3D
761 Returns
762 =======
764 Point3D
766 Examples
767 ========
769 >>> from sympy import Plane, Point3D
770 >>> A = Plane(Point3D(1, 1, 2), normal_vector=(1, 1, 1))
772 The projection is along the normal vector direction, not the z
773 axis, so (1, 1) does not project to (1, 1, 2) on the plane A:
775 >>> b = Point3D(1, 1)
776 >>> A.projection(b)
777 Point3D(5/3, 5/3, 2/3)
778 >>> _ in A
779 True
781 But the point (1, 1, 2) projects to (1, 1) on the XY-plane:
783 >>> XY = Plane((0, 0, 0), (0, 0, 1))
784 >>> XY.projection((1, 1, 2))
785 Point3D(1, 1, 0)
786 """
787 rv = Point(pt, dim=3)
788 if rv in self:
789 return rv
790 return self.intersection(Line3D(rv, rv + Point3D(self.normal_vector)))[0]
792 def random_point(self, seed=None):
793 """ Returns a random point on the Plane.
795 Returns
796 =======
798 Point3D
800 Examples
801 ========
803 >>> from sympy import Plane
804 >>> p = Plane((1, 0, 0), normal_vector=(0, 1, 0))
805 >>> r = p.random_point(seed=42) # seed value is optional
806 >>> r.n(3)
807 Point3D(2.29, 0, -1.35)
809 The random point can be moved to lie on the circle of radius
810 1 centered on p1:
812 >>> c = p.p1 + (r - p.p1).unit
813 >>> c.distance(p.p1).equals(1)
814 True
815 """
816 if seed is not None:
817 rng = random.Random(seed)
818 else:
819 rng = random
820 params = {
821 x: 2*Rational(rng.gauss(0, 1)) - 1,
822 y: 2*Rational(rng.gauss(0, 1)) - 1}
823 return self.arbitrary_point(x, y).subs(params)
825 def parameter_value(self, other, u, v=None):
826 """Return the parameter(s) corresponding to the given point.
828 Examples
829 ========
831 >>> from sympy import pi, Plane
832 >>> from sympy.abc import t, u, v
833 >>> p = Plane((2, 0, 0), (0, 0, 1), (0, 1, 0))
835 By default, the parameter value returned defines a point
836 that is a distance of 1 from the Plane's p1 value and
837 in line with the given point:
839 >>> on_circle = p.arbitrary_point(t).subs(t, pi/4)
840 >>> on_circle.distance(p.p1)
841 1
842 >>> p.parameter_value(on_circle, t)
843 {t: pi/4}
845 Moving the point twice as far from p1 does not change
846 the parameter value:
848 >>> off_circle = p.p1 + (on_circle - p.p1)*2
849 >>> off_circle.distance(p.p1)
850 2
851 >>> p.parameter_value(off_circle, t)
852 {t: pi/4}
854 If the 2-value parameter is desired, supply the two
855 parameter symbols and a replacement dictionary will
856 be returned:
858 >>> p.parameter_value(on_circle, u, v)
859 {u: sqrt(10)/10, v: sqrt(10)/30}
860 >>> p.parameter_value(off_circle, u, v)
861 {u: sqrt(10)/5, v: sqrt(10)/15}
862 """
863 if not isinstance(other, GeometryEntity):
864 other = Point(other, dim=self.ambient_dimension)
865 if not isinstance(other, Point):
866 raise ValueError("other must be a point")
867 if other == self.p1:
868 return other
869 if isinstance(u, Symbol) and v is None:
870 delta = self.arbitrary_point(u) - self.p1
871 eq = delta - (other - self.p1).unit
872 sol = solve(eq, u, dict=True)
873 elif isinstance(u, Symbol) and isinstance(v, Symbol):
874 pt = self.arbitrary_point(u, v)
875 sol = solve(pt - other, (u, v), dict=True)
876 else:
877 raise ValueError('expecting 1 or 2 symbols')
878 if not sol:
879 raise ValueError("Given point is not on %s" % func_name(self))
880 return sol[0] # {t: tval} or {u: uval, v: vval}
882 @property
883 def ambient_dimension(self):
884 return self.p1.ambient_dimension