123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887 |
- """Geometrical Planes.
- Contains
- ========
- Plane
- """
- from sympy.core import Dummy, Rational, S, Symbol
- from sympy.core.symbol import _symbol
- from sympy.functions.elementary.trigonometric import cos, sin, acos, asin, sqrt
- from .entity import GeometryEntity
- from .line import (Line, Ray, Segment, Line3D, LinearEntity, LinearEntity3D,
- Ray3D, Segment3D)
- from .point import Point, Point3D
- from sympy.matrices import Matrix
- from sympy.polys.polytools import cancel
- from sympy.solvers import solve, linsolve
- from sympy.utilities.iterables import uniq, is_sequence
- from sympy.utilities.misc import filldedent, func_name, Undecidable
- from mpmath.libmp.libmpf import prec_to_dps
- import random
- class Plane(GeometryEntity):
- """
- A plane is a flat, two-dimensional surface. A plane is the two-dimensional
- analogue of a point (zero-dimensions), a line (one-dimension) and a solid
- (three-dimensions). A plane can generally be constructed by two types of
- inputs. They are three non-collinear points and a point and the plane's
- normal vector.
- Attributes
- ==========
- p1
- normal_vector
- Examples
- ========
- >>> from sympy import Plane, Point3D
- >>> Plane(Point3D(1, 1, 1), Point3D(2, 3, 4), Point3D(2, 2, 2))
- Plane(Point3D(1, 1, 1), (-1, 2, -1))
- >>> Plane((1, 1, 1), (2, 3, 4), (2, 2, 2))
- Plane(Point3D(1, 1, 1), (-1, 2, -1))
- >>> Plane(Point3D(1, 1, 1), normal_vector=(1,4,7))
- Plane(Point3D(1, 1, 1), (1, 4, 7))
- """
- def __new__(cls, p1, a=None, b=None, **kwargs):
- p1 = Point3D(p1, dim=3)
- if a and b:
- p2 = Point(a, dim=3)
- p3 = Point(b, dim=3)
- if Point3D.are_collinear(p1, p2, p3):
- raise ValueError('Enter three non-collinear points')
- a = p1.direction_ratio(p2)
- b = p1.direction_ratio(p3)
- normal_vector = tuple(Matrix(a).cross(Matrix(b)))
- else:
- a = kwargs.pop('normal_vector', a)
- evaluate = kwargs.get('evaluate', True)
- if is_sequence(a) and len(a) == 3:
- normal_vector = Point3D(a).args if evaluate else a
- else:
- raise ValueError(filldedent('''
- Either provide 3 3D points or a point with a
- normal vector expressed as a sequence of length 3'''))
- if all(coord.is_zero for coord in normal_vector):
- raise ValueError('Normal vector cannot be zero vector')
- return GeometryEntity.__new__(cls, p1, normal_vector, **kwargs)
- def __contains__(self, o):
- x, y, z = map(Dummy, 'xyz')
- k = self.equation(x, y, z)
- if isinstance(o, (LinearEntity, LinearEntity3D)):
- t = Dummy()
- d = Point3D(o.arbitrary_point(t))
- e = k.subs([(x, d.x), (y, d.y), (z, d.z)])
- return e.equals(0)
- try:
- o = Point(o, dim=3, strict=True)
- d = k.xreplace(dict(zip((x, y, z), o.args)))
- return d.equals(0)
- except TypeError:
- return False
- def _eval_evalf(self, prec=15, **options):
- pt, tup = self.args
- dps = prec_to_dps(prec)
- pt = pt.evalf(n=dps, **options)
- tup = tuple([i.evalf(n=dps, **options) for i in tup])
- return self.func(pt, normal_vector=tup, evaluate=False)
- def angle_between(self, o):
- """Angle between the plane and other geometric entity.
- Parameters
- ==========
- LinearEntity3D, Plane.
- Returns
- =======
- angle : angle in radians
- Notes
- =====
- This method accepts only 3D entities as it's parameter, but if you want
- to calculate the angle between a 2D entity and a plane you should
- first convert to a 3D entity by projecting onto a desired plane and
- then proceed to calculate the angle.
- Examples
- ========
- >>> from sympy import Point3D, Line3D, Plane
- >>> a = Plane(Point3D(1, 2, 2), normal_vector=(1, 2, 3))
- >>> b = Line3D(Point3D(1, 3, 4), Point3D(2, 2, 2))
- >>> a.angle_between(b)
- -asin(sqrt(21)/6)
- """
- if isinstance(o, LinearEntity3D):
- a = Matrix(self.normal_vector)
- b = Matrix(o.direction_ratio)
- c = a.dot(b)
- d = sqrt(sum([i**2 for i in self.normal_vector]))
- e = sqrt(sum([i**2 for i in o.direction_ratio]))
- return asin(c/(d*e))
- if isinstance(o, Plane):
- a = Matrix(self.normal_vector)
- b = Matrix(o.normal_vector)
- c = a.dot(b)
- d = sqrt(sum([i**2 for i in self.normal_vector]))
- e = sqrt(sum([i**2 for i in o.normal_vector]))
- return acos(c/(d*e))
- def arbitrary_point(self, u=None, v=None):
- """ Returns an arbitrary point on the Plane. If given two
- parameters, the point ranges over the entire plane. If given 1
- or no parameters, returns a point with one parameter which,
- when varying from 0 to 2*pi, moves the point in a circle of
- radius 1 about p1 of the Plane.
- Examples
- ========
- >>> from sympy import Plane, Ray
- >>> from sympy.abc import u, v, t, r
- >>> p = Plane((1, 1, 1), normal_vector=(1, 0, 0))
- >>> p.arbitrary_point(u, v)
- Point3D(1, u + 1, v + 1)
- >>> p.arbitrary_point(t)
- Point3D(1, cos(t) + 1, sin(t) + 1)
- While arbitrary values of u and v can move the point anywhere in
- the plane, the single-parameter point can be used to construct a
- ray whose arbitrary point can be located at angle t and radius
- r from p.p1:
- >>> Ray(p.p1, _).arbitrary_point(r)
- Point3D(1, r*cos(t) + 1, r*sin(t) + 1)
- Returns
- =======
- Point3D
- """
- circle = v is None
- if circle:
- u = _symbol(u or 't', real=True)
- else:
- u = _symbol(u or 'u', real=True)
- v = _symbol(v or 'v', real=True)
- x, y, z = self.normal_vector
- a, b, c = self.p1.args
- # x1, y1, z1 is a nonzero vector parallel to the plane
- if x.is_zero and y.is_zero:
- x1, y1, z1 = S.One, S.Zero, S.Zero
- else:
- x1, y1, z1 = -y, x, S.Zero
- # x2, y2, z2 is also parallel to the plane, and orthogonal to x1, y1, z1
- x2, y2, z2 = tuple(Matrix((x, y, z)).cross(Matrix((x1, y1, z1))))
- if circle:
- x1, y1, z1 = (w/sqrt(x1**2 + y1**2 + z1**2) for w in (x1, y1, z1))
- x2, y2, z2 = (w/sqrt(x2**2 + y2**2 + z2**2) for w in (x2, y2, z2))
- p = Point3D(a + x1*cos(u) + x2*sin(u), \
- b + y1*cos(u) + y2*sin(u), \
- c + z1*cos(u) + z2*sin(u))
- else:
- p = Point3D(a + x1*u + x2*v, b + y1*u + y2*v, c + z1*u + z2*v)
- return p
- @staticmethod
- def are_concurrent(*planes):
- """Is a sequence of Planes concurrent?
- Two or more Planes are concurrent if their intersections
- are a common line.
- Parameters
- ==========
- planes: list
- Returns
- =======
- Boolean
- Examples
- ========
- >>> from sympy import Plane, Point3D
- >>> a = Plane(Point3D(5, 0, 0), normal_vector=(1, -1, 1))
- >>> b = Plane(Point3D(0, -2, 0), normal_vector=(3, 1, 1))
- >>> c = Plane(Point3D(0, -1, 0), normal_vector=(5, -1, 9))
- >>> Plane.are_concurrent(a, b)
- True
- >>> Plane.are_concurrent(a, b, c)
- False
- """
- planes = list(uniq(planes))
- for i in planes:
- if not isinstance(i, Plane):
- raise ValueError('All objects should be Planes but got %s' % i.func)
- if len(planes) < 2:
- return False
- planes = list(planes)
- first = planes.pop(0)
- sol = first.intersection(planes[0])
- if sol == []:
- return False
- else:
- line = sol[0]
- for i in planes[1:]:
- l = first.intersection(i)
- if not l or l[0] not in line:
- return False
- return True
- def distance(self, o):
- """Distance between the plane and another geometric entity.
- Parameters
- ==========
- Point3D, LinearEntity3D, Plane.
- Returns
- =======
- distance
- Notes
- =====
- This method accepts only 3D entities as it's parameter, but if you want
- to calculate the distance between a 2D entity and a plane you should
- first convert to a 3D entity by projecting onto a desired plane and
- then proceed to calculate the distance.
- Examples
- ========
- >>> from sympy import Point3D, Line3D, Plane
- >>> a = Plane(Point3D(1, 1, 1), normal_vector=(1, 1, 1))
- >>> b = Point3D(1, 2, 3)
- >>> a.distance(b)
- sqrt(3)
- >>> c = Line3D(Point3D(2, 3, 1), Point3D(1, 2, 2))
- >>> a.distance(c)
- 0
- """
- if self.intersection(o) != []:
- return S.Zero
- if isinstance(o, (Segment3D, Ray3D)):
- a, b = o.p1, o.p2
- pi, = self.intersection(Line3D(a, b))
- if pi in o:
- return self.distance(pi)
- elif a in Segment3D(pi, b):
- return self.distance(a)
- else:
- assert isinstance(o, Segment3D) is True
- return self.distance(b)
- # following code handles `Point3D`, `LinearEntity3D`, `Plane`
- a = o if isinstance(o, Point3D) else o.p1
- n = Point3D(self.normal_vector).unit
- d = (a - self.p1).dot(n)
- return abs(d)
- def equals(self, o):
- """
- Returns True if self and o are the same mathematical entities.
- Examples
- ========
- >>> from sympy import Plane, Point3D
- >>> a = Plane(Point3D(1, 2, 3), normal_vector=(1, 1, 1))
- >>> b = Plane(Point3D(1, 2, 3), normal_vector=(2, 2, 2))
- >>> c = Plane(Point3D(1, 2, 3), normal_vector=(-1, 4, 6))
- >>> a.equals(a)
- True
- >>> a.equals(b)
- True
- >>> a.equals(c)
- False
- """
- if isinstance(o, Plane):
- a = self.equation()
- b = o.equation()
- return cancel(a/b).is_constant()
- else:
- return False
- def equation(self, x=None, y=None, z=None):
- """The equation of the Plane.
- Examples
- ========
- >>> from sympy import Point3D, Plane
- >>> a = Plane(Point3D(1, 1, 2), Point3D(2, 4, 7), Point3D(3, 5, 1))
- >>> a.equation()
- -23*x + 11*y - 2*z + 16
- >>> a = Plane(Point3D(1, 4, 2), normal_vector=(6, 6, 6))
- >>> a.equation()
- 6*x + 6*y + 6*z - 42
- """
- x, y, z = [i if i else Symbol(j, real=True) for i, j in zip((x, y, z), 'xyz')]
- a = Point3D(x, y, z)
- b = self.p1.direction_ratio(a)
- c = self.normal_vector
- return (sum(i*j for i, j in zip(b, c)))
- def intersection(self, o):
- """ The intersection with other geometrical entity.
- Parameters
- ==========
- Point, Point3D, LinearEntity, LinearEntity3D, Plane
- Returns
- =======
- List
- Examples
- ========
- >>> from sympy import Point3D, Line3D, Plane
- >>> a = Plane(Point3D(1, 2, 3), normal_vector=(1, 1, 1))
- >>> b = Point3D(1, 2, 3)
- >>> a.intersection(b)
- [Point3D(1, 2, 3)]
- >>> c = Line3D(Point3D(1, 4, 7), Point3D(2, 2, 2))
- >>> a.intersection(c)
- [Point3D(2, 2, 2)]
- >>> d = Plane(Point3D(6, 0, 0), normal_vector=(2, -5, 3))
- >>> e = Plane(Point3D(2, 0, 0), normal_vector=(3, 4, -3))
- >>> d.intersection(e)
- [Line3D(Point3D(78/23, -24/23, 0), Point3D(147/23, 321/23, 23))]
- """
- if not isinstance(o, GeometryEntity):
- o = Point(o, dim=3)
- if isinstance(o, Point):
- if o in self:
- return [o]
- else:
- return []
- if isinstance(o, (LinearEntity, LinearEntity3D)):
- # recast to 3D
- p1, p2 = o.p1, o.p2
- if isinstance(o, Segment):
- o = Segment3D(p1, p2)
- elif isinstance(o, Ray):
- o = Ray3D(p1, p2)
- elif isinstance(o, Line):
- o = Line3D(p1, p2)
- else:
- raise ValueError('unhandled linear entity: %s' % o.func)
- if o in self:
- return [o]
- else:
- t = Dummy() # unnamed else it may clash with a symbol in o
- a = Point3D(o.arbitrary_point(t))
- p1, n = self.p1, Point3D(self.normal_vector)
- # TODO: Replace solve with solveset, when this line is tested
- c = solve((a - p1).dot(n), t)
- if not c:
- return []
- else:
- c = [i for i in c if i.is_real is not False]
- if len(c) > 1:
- c = [i for i in c if i.is_real]
- if len(c) != 1:
- raise Undecidable("not sure which point is real")
- p = a.subs(t, c[0])
- if p not in o:
- return [] # e.g. a segment might not intersect a plane
- return [p]
- if isinstance(o, Plane):
- if self.equals(o):
- return [self]
- if self.is_parallel(o):
- return []
- else:
- x, y, z = map(Dummy, 'xyz')
- a, b = Matrix([self.normal_vector]), Matrix([o.normal_vector])
- c = list(a.cross(b))
- d = self.equation(x, y, z)
- e = o.equation(x, y, z)
- result = list(linsolve([d, e], x, y, z))[0]
- for i in (x, y, z): result = result.subs(i, 0)
- return [Line3D(Point3D(result), direction_ratio=c)]
- def is_coplanar(self, o):
- """ Returns True if `o` is coplanar with self, else False.
- Examples
- ========
- >>> from sympy import Plane
- >>> o = (0, 0, 0)
- >>> p = Plane(o, (1, 1, 1))
- >>> p2 = Plane(o, (2, 2, 2))
- >>> p == p2
- False
- >>> p.is_coplanar(p2)
- True
- """
- if isinstance(o, Plane):
- x, y, z = map(Dummy, 'xyz')
- return not cancel(self.equation(x, y, z)/o.equation(x, y, z)).has(x, y, z)
- if isinstance(o, Point3D):
- return o in self
- elif isinstance(o, LinearEntity3D):
- return all(i in self for i in self)
- elif isinstance(o, GeometryEntity): # XXX should only be handling 2D objects now
- return all(i == 0 for i in self.normal_vector[:2])
- def is_parallel(self, l):
- """Is the given geometric entity parallel to the plane?
- Parameters
- ==========
- LinearEntity3D or Plane
- Returns
- =======
- Boolean
- Examples
- ========
- >>> from sympy import Plane, Point3D
- >>> a = Plane(Point3D(1,4,6), normal_vector=(2, 4, 6))
- >>> b = Plane(Point3D(3,1,3), normal_vector=(4, 8, 12))
- >>> a.is_parallel(b)
- True
- """
- if isinstance(l, LinearEntity3D):
- a = l.direction_ratio
- b = self.normal_vector
- c = sum([i*j for i, j in zip(a, b)])
- if c == 0:
- return True
- else:
- return False
- elif isinstance(l, Plane):
- a = Matrix(l.normal_vector)
- b = Matrix(self.normal_vector)
- if a.cross(b).is_zero_matrix:
- return True
- else:
- return False
- def is_perpendicular(self, l):
- """is the given geometric entity perpendicualar to the given plane?
- Parameters
- ==========
- LinearEntity3D or Plane
- Returns
- =======
- Boolean
- Examples
- ========
- >>> from sympy import Plane, Point3D
- >>> a = Plane(Point3D(1,4,6), normal_vector=(2, 4, 6))
- >>> b = Plane(Point3D(2, 2, 2), normal_vector=(-1, 2, -1))
- >>> a.is_perpendicular(b)
- True
- """
- if isinstance(l, LinearEntity3D):
- a = Matrix(l.direction_ratio)
- b = Matrix(self.normal_vector)
- if a.cross(b).is_zero_matrix:
- return True
- else:
- return False
- elif isinstance(l, Plane):
- a = Matrix(l.normal_vector)
- b = Matrix(self.normal_vector)
- if a.dot(b) == 0:
- return True
- else:
- return False
- else:
- return False
- @property
- def normal_vector(self):
- """Normal vector of the given plane.
- Examples
- ========
- >>> from sympy import Point3D, Plane
- >>> a = Plane(Point3D(1, 1, 1), Point3D(2, 3, 4), Point3D(2, 2, 2))
- >>> a.normal_vector
- (-1, 2, -1)
- >>> a = Plane(Point3D(1, 1, 1), normal_vector=(1, 4, 7))
- >>> a.normal_vector
- (1, 4, 7)
- """
- return self.args[1]
- @property
- def p1(self):
- """The only defining point of the plane. Others can be obtained from the
- arbitrary_point method.
- See Also
- ========
- sympy.geometry.point.Point3D
- Examples
- ========
- >>> from sympy import Point3D, Plane
- >>> a = Plane(Point3D(1, 1, 1), Point3D(2, 3, 4), Point3D(2, 2, 2))
- >>> a.p1
- Point3D(1, 1, 1)
- """
- return self.args[0]
- def parallel_plane(self, pt):
- """
- Plane parallel to the given plane and passing through the point pt.
- Parameters
- ==========
- pt: Point3D
- Returns
- =======
- Plane
- Examples
- ========
- >>> from sympy import Plane, Point3D
- >>> a = Plane(Point3D(1, 4, 6), normal_vector=(2, 4, 6))
- >>> a.parallel_plane(Point3D(2, 3, 5))
- Plane(Point3D(2, 3, 5), (2, 4, 6))
- """
- a = self.normal_vector
- return Plane(pt, normal_vector=a)
- def perpendicular_line(self, pt):
- """A line perpendicular to the given plane.
- Parameters
- ==========
- pt: Point3D
- Returns
- =======
- Line3D
- Examples
- ========
- >>> from sympy import Plane, Point3D
- >>> a = Plane(Point3D(1,4,6), normal_vector=(2, 4, 6))
- >>> a.perpendicular_line(Point3D(9, 8, 7))
- Line3D(Point3D(9, 8, 7), Point3D(11, 12, 13))
- """
- a = self.normal_vector
- return Line3D(pt, direction_ratio=a)
- def perpendicular_plane(self, *pts):
- """
- Return a perpendicular passing through the given points. If the
- direction ratio between the points is the same as the Plane's normal
- vector then, to select from the infinite number of possible planes,
- a third point will be chosen on the z-axis (or the y-axis
- if the normal vector is already parallel to the z-axis). If less than
- two points are given they will be supplied as follows: if no point is
- given then pt1 will be self.p1; if a second point is not given it will
- be a point through pt1 on a line parallel to the z-axis (if the normal
- is not already the z-axis, otherwise on the line parallel to the
- y-axis).
- Parameters
- ==========
- pts: 0, 1 or 2 Point3D
- Returns
- =======
- Plane
- Examples
- ========
- >>> from sympy import Plane, Point3D
- >>> a, b = Point3D(0, 0, 0), Point3D(0, 1, 0)
- >>> Z = (0, 0, 1)
- >>> p = Plane(a, normal_vector=Z)
- >>> p.perpendicular_plane(a, b)
- Plane(Point3D(0, 0, 0), (1, 0, 0))
- """
- if len(pts) > 2:
- raise ValueError('No more than 2 pts should be provided.')
- pts = list(pts)
- if len(pts) == 0:
- pts.append(self.p1)
- if len(pts) == 1:
- x, y, z = self.normal_vector
- if x == y == 0:
- dir = (0, 1, 0)
- else:
- dir = (0, 0, 1)
- pts.append(pts[0] + Point3D(*dir))
- p1, p2 = [Point(i, dim=3) for i in pts]
- l = Line3D(p1, p2)
- n = Line3D(p1, direction_ratio=self.normal_vector)
- if l in n: # XXX should an error be raised instead?
- # there are infinitely many perpendicular planes;
- x, y, z = self.normal_vector
- if x == y == 0:
- # the z axis is the normal so pick a pt on the y-axis
- p3 = Point3D(0, 1, 0) # case 1
- else:
- # else pick a pt on the z axis
- p3 = Point3D(0, 0, 1) # case 2
- # in case that point is already given, move it a bit
- if p3 in l:
- p3 *= 2 # case 3
- else:
- p3 = p1 + Point3D(*self.normal_vector) # case 4
- return Plane(p1, p2, p3)
- def projection_line(self, line):
- """Project the given line onto the plane through the normal plane
- containing the line.
- Parameters
- ==========
- LinearEntity or LinearEntity3D
- Returns
- =======
- Point3D, Line3D, Ray3D or Segment3D
- Notes
- =====
- For the interaction between 2D and 3D lines(segments, rays), you should
- convert the line to 3D by using this method. For example for finding the
- intersection between a 2D and a 3D line, convert the 2D line to a 3D line
- by projecting it on a required plane and then proceed to find the
- intersection between those lines.
- Examples
- ========
- >>> from sympy import Plane, Line, Line3D, Point3D
- >>> a = Plane(Point3D(1, 1, 1), normal_vector=(1, 1, 1))
- >>> b = Line(Point3D(1, 1), Point3D(2, 2))
- >>> a.projection_line(b)
- Line3D(Point3D(4/3, 4/3, 1/3), Point3D(5/3, 5/3, -1/3))
- >>> c = Line3D(Point3D(1, 1, 1), Point3D(2, 2, 2))
- >>> a.projection_line(c)
- Point3D(1, 1, 1)
- """
- if not isinstance(line, (LinearEntity, LinearEntity3D)):
- raise NotImplementedError('Enter a linear entity only')
- a, b = self.projection(line.p1), self.projection(line.p2)
- if a == b:
- # projection does not imply intersection so for
- # this case (line parallel to plane's normal) we
- # return the projection point
- return a
- if isinstance(line, (Line, Line3D)):
- return Line3D(a, b)
- if isinstance(line, (Ray, Ray3D)):
- return Ray3D(a, b)
- if isinstance(line, (Segment, Segment3D)):
- return Segment3D(a, b)
- def projection(self, pt):
- """Project the given point onto the plane along the plane normal.
- Parameters
- ==========
- Point or Point3D
- Returns
- =======
- Point3D
- Examples
- ========
- >>> from sympy import Plane, Point3D
- >>> A = Plane(Point3D(1, 1, 2), normal_vector=(1, 1, 1))
- The projection is along the normal vector direction, not the z
- axis, so (1, 1) does not project to (1, 1, 2) on the plane A:
- >>> b = Point3D(1, 1)
- >>> A.projection(b)
- Point3D(5/3, 5/3, 2/3)
- >>> _ in A
- True
- But the point (1, 1, 2) projects to (1, 1) on the XY-plane:
- >>> XY = Plane((0, 0, 0), (0, 0, 1))
- >>> XY.projection((1, 1, 2))
- Point3D(1, 1, 0)
- """
- rv = Point(pt, dim=3)
- if rv in self:
- return rv
- return self.intersection(Line3D(rv, rv + Point3D(self.normal_vector)))[0]
- def random_point(self, seed=None):
- """ Returns a random point on the Plane.
- Returns
- =======
- Point3D
- Examples
- ========
- >>> from sympy import Plane
- >>> p = Plane((1, 0, 0), normal_vector=(0, 1, 0))
- >>> r = p.random_point(seed=42) # seed value is optional
- >>> r.n(3)
- Point3D(2.29, 0, -1.35)
- The random point can be moved to lie on the circle of radius
- 1 centered on p1:
- >>> c = p.p1 + (r - p.p1).unit
- >>> c.distance(p.p1).equals(1)
- True
- """
- if seed is not None:
- rng = random.Random(seed)
- else:
- rng = random
- u, v = Dummy('u'), Dummy('v')
- params = {
- u: 2*Rational(rng.gauss(0, 1)) - 1,
- v: 2*Rational(rng.gauss(0, 1)) - 1}
- return self.arbitrary_point(u, v).subs(params)
- def parameter_value(self, other, u, v=None):
- """Return the parameter(s) corresponding to the given point.
- Examples
- ========
- >>> from sympy import pi, Plane
- >>> from sympy.abc import t, u, v
- >>> p = Plane((2, 0, 0), (0, 0, 1), (0, 1, 0))
- By default, the parameter value returned defines a point
- that is a distance of 1 from the Plane's p1 value and
- in line with the given point:
- >>> on_circle = p.arbitrary_point(t).subs(t, pi/4)
- >>> on_circle.distance(p.p1)
- 1
- >>> p.parameter_value(on_circle, t)
- {t: pi/4}
- Moving the point twice as far from p1 does not change
- the parameter value:
- >>> off_circle = p.p1 + (on_circle - p.p1)*2
- >>> off_circle.distance(p.p1)
- 2
- >>> p.parameter_value(off_circle, t)
- {t: pi/4}
- If the 2-value parameter is desired, supply the two
- parameter symbols and a replacement dictionary will
- be returned:
- >>> p.parameter_value(on_circle, u, v)
- {u: sqrt(10)/10, v: sqrt(10)/30}
- >>> p.parameter_value(off_circle, u, v)
- {u: sqrt(10)/5, v: sqrt(10)/15}
- """
- from sympy.geometry.point import Point
- if not isinstance(other, GeometryEntity):
- other = Point(other, dim=self.ambient_dimension)
- if not isinstance(other, Point):
- raise ValueError("other must be a point")
- if other == self.p1:
- return other
- if isinstance(u, Symbol) and v is None:
- delta = self.arbitrary_point(u) - self.p1
- eq = delta - (other - self.p1).unit
- sol = solve(eq, u, dict=True)
- elif isinstance(u, Symbol) and isinstance(v, Symbol):
- pt = self.arbitrary_point(u, v)
- sol = solve(pt - other, (u, v), dict=True)
- else:
- raise ValueError('expecting 1 or 2 symbols')
- if not sol:
- raise ValueError("Given point is not on %s" % func_name(self))
- return sol[0] # {t: tval} or {u: uval, v: vval}
- @property
- def ambient_dimension(self):
- return self.p1.ambient_dimension
|