Spaces:
Sleeping
Sleeping
"""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 | |
x, y, z, t = [Dummy('plane_dummy') for i in range(4)] | |
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 | |
- 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): | |
k = self.equation(x, y, z) | |
if isinstance(o, (LinearEntity, LinearEntity3D)): | |
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 | |
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: | |
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): | |
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 | |
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] | |
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 | |
params = { | |
x: 2*Rational(rng.gauss(0, 1)) - 1, | |
y: 2*Rational(rng.gauss(0, 1)) - 1} | |
return self.arbitrary_point(x, y).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} | |
""" | |
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} | |
def ambient_dimension(self): | |
return self.p1.ambient_dimension | |