Spaces:
Sleeping
Sleeping
"""Geometrical Points. | |
Contains | |
======== | |
Point | |
Point2D | |
Point3D | |
When methods of Point require 1 or more points as arguments, they | |
can be passed as a sequence of coordinates or Points: | |
from sympy import Point | |
Point(1, 1).is_collinear((2, 2), (3, 4)) | |
False | |
Point(1, 1).is_collinear(Point(2, 2), Point(3, 4)) | |
False | |
""" | |
import warnings | |
from sympy.core import S, sympify, Expr | |
from sympy.core.add import Add | |
from sympy.core.containers import Tuple | |
from sympy.core.numbers import Float | |
from sympy.core.parameters import global_parameters | |
from sympy.simplify import nsimplify, simplify | |
from sympy.geometry.exceptions import GeometryError | |
from sympy.functions.elementary.miscellaneous import sqrt | |
from sympy.functions.elementary.complexes import im | |
from sympy.functions.elementary.trigonometric import cos, sin | |
from sympy.matrices import Matrix | |
from sympy.matrices.expressions import Transpose | |
from sympy.utilities.iterables import uniq, is_sequence | |
from sympy.utilities.misc import filldedent, func_name, Undecidable | |
from .entity import GeometryEntity | |
from mpmath.libmp.libmpf import prec_to_dps | |
class Point(GeometryEntity): | |
"""A point in a n-dimensional Euclidean space. | |
Parameters | |
========== | |
coords : sequence of n-coordinate values. In the special | |
case where n=2 or 3, a Point2D or Point3D will be created | |
as appropriate. | |
evaluate : if `True` (default), all floats are turn into | |
exact types. | |
dim : number of coordinates the point should have. If coordinates | |
are unspecified, they are padded with zeros. | |
on_morph : indicates what should happen when the number of | |
coordinates of a point need to be changed by adding or | |
removing zeros. Possible values are `'warn'`, `'error'`, or | |
`ignore` (default). No warning or error is given when `*args` | |
is empty and `dim` is given. An error is always raised when | |
trying to remove nonzero coordinates. | |
Attributes | |
========== | |
length | |
origin: A `Point` representing the origin of the | |
appropriately-dimensioned space. | |
Raises | |
====== | |
TypeError : When instantiating with anything but a Point or sequence | |
ValueError : when instantiating with a sequence with length < 2 or | |
when trying to reduce dimensions if keyword `on_morph='error'` is | |
set. | |
See Also | |
======== | |
sympy.geometry.line.Segment : Connects two Points | |
Examples | |
======== | |
>>> from sympy import Point | |
>>> from sympy.abc import x | |
>>> Point(1, 2, 3) | |
Point3D(1, 2, 3) | |
>>> Point([1, 2]) | |
Point2D(1, 2) | |
>>> Point(0, x) | |
Point2D(0, x) | |
>>> Point(dim=4) | |
Point(0, 0, 0, 0) | |
Floats are automatically converted to Rational unless the | |
evaluate flag is False: | |
>>> Point(0.5, 0.25) | |
Point2D(1/2, 1/4) | |
>>> Point(0.5, 0.25, evaluate=False) | |
Point2D(0.5, 0.25) | |
""" | |
is_Point = True | |
def __new__(cls, *args, **kwargs): | |
evaluate = kwargs.get('evaluate', global_parameters.evaluate) | |
on_morph = kwargs.get('on_morph', 'ignore') | |
# unpack into coords | |
coords = args[0] if len(args) == 1 else args | |
# check args and handle quickly handle Point instances | |
if isinstance(coords, Point): | |
# even if we're mutating the dimension of a point, we | |
# don't reevaluate its coordinates | |
evaluate = False | |
if len(coords) == kwargs.get('dim', len(coords)): | |
return coords | |
if not is_sequence(coords): | |
raise TypeError(filldedent(''' | |
Expecting sequence of coordinates, not `{}`''' | |
.format(func_name(coords)))) | |
# A point where only `dim` is specified is initialized | |
# to zeros. | |
if len(coords) == 0 and kwargs.get('dim', None): | |
coords = (S.Zero,)*kwargs.get('dim') | |
coords = Tuple(*coords) | |
dim = kwargs.get('dim', len(coords)) | |
if len(coords) < 2: | |
raise ValueError(filldedent(''' | |
Point requires 2 or more coordinates or | |
keyword `dim` > 1.''')) | |
if len(coords) != dim: | |
message = ("Dimension of {} needs to be changed " | |
"from {} to {}.").format(coords, len(coords), dim) | |
if on_morph == 'ignore': | |
pass | |
elif on_morph == "error": | |
raise ValueError(message) | |
elif on_morph == 'warn': | |
warnings.warn(message, stacklevel=2) | |
else: | |
raise ValueError(filldedent(''' | |
on_morph value should be 'error', | |
'warn' or 'ignore'.''')) | |
if any(coords[dim:]): | |
raise ValueError('Nonzero coordinates cannot be removed.') | |
if any(a.is_number and im(a).is_zero is False for a in coords): | |
raise ValueError('Imaginary coordinates are not permitted.') | |
if not all(isinstance(a, Expr) for a in coords): | |
raise TypeError('Coordinates must be valid SymPy expressions.') | |
# pad with zeros appropriately | |
coords = coords[:dim] + (S.Zero,)*(dim - len(coords)) | |
# Turn any Floats into rationals and simplify | |
# any expressions before we instantiate | |
if evaluate: | |
coords = coords.xreplace({ | |
f: simplify(nsimplify(f, rational=True)) | |
for f in coords.atoms(Float)}) | |
# return 2D or 3D instances | |
if len(coords) == 2: | |
kwargs['_nocheck'] = True | |
return Point2D(*coords, **kwargs) | |
elif len(coords) == 3: | |
kwargs['_nocheck'] = True | |
return Point3D(*coords, **kwargs) | |
# the general Point | |
return GeometryEntity.__new__(cls, *coords) | |
def __abs__(self): | |
"""Returns the distance between this point and the origin.""" | |
origin = Point([0]*len(self)) | |
return Point.distance(origin, self) | |
def __add__(self, other): | |
"""Add other to self by incrementing self's coordinates by | |
those of other. | |
Notes | |
===== | |
>>> from sympy import Point | |
When sequences of coordinates are passed to Point methods, they | |
are converted to a Point internally. This __add__ method does | |
not do that so if floating point values are used, a floating | |
point result (in terms of SymPy Floats) will be returned. | |
>>> Point(1, 2) + (.1, .2) | |
Point2D(1.1, 2.2) | |
If this is not desired, the `translate` method can be used or | |
another Point can be added: | |
>>> Point(1, 2).translate(.1, .2) | |
Point2D(11/10, 11/5) | |
>>> Point(1, 2) + Point(.1, .2) | |
Point2D(11/10, 11/5) | |
See Also | |
======== | |
sympy.geometry.point.Point.translate | |
""" | |
try: | |
s, o = Point._normalize_dimension(self, Point(other, evaluate=False)) | |
except TypeError: | |
raise GeometryError("Don't know how to add {} and a Point object".format(other)) | |
coords = [simplify(a + b) for a, b in zip(s, o)] | |
return Point(coords, evaluate=False) | |
def __contains__(self, item): | |
return item in self.args | |
def __truediv__(self, divisor): | |
"""Divide point's coordinates by a factor.""" | |
divisor = sympify(divisor) | |
coords = [simplify(x/divisor) for x in self.args] | |
return Point(coords, evaluate=False) | |
def __eq__(self, other): | |
if not isinstance(other, Point) or len(self.args) != len(other.args): | |
return False | |
return self.args == other.args | |
def __getitem__(self, key): | |
return self.args[key] | |
def __hash__(self): | |
return hash(self.args) | |
def __iter__(self): | |
return self.args.__iter__() | |
def __len__(self): | |
return len(self.args) | |
def __mul__(self, factor): | |
"""Multiply point's coordinates by a factor. | |
Notes | |
===== | |
>>> from sympy import Point | |
When multiplying a Point by a floating point number, | |
the coordinates of the Point will be changed to Floats: | |
>>> Point(1, 2)*0.1 | |
Point2D(0.1, 0.2) | |
If this is not desired, the `scale` method can be used or | |
else only multiply or divide by integers: | |
>>> Point(1, 2).scale(1.1, 1.1) | |
Point2D(11/10, 11/5) | |
>>> Point(1, 2)*11/10 | |
Point2D(11/10, 11/5) | |
See Also | |
======== | |
sympy.geometry.point.Point.scale | |
""" | |
factor = sympify(factor) | |
coords = [simplify(x*factor) for x in self.args] | |
return Point(coords, evaluate=False) | |
def __rmul__(self, factor): | |
"""Multiply a factor by point's coordinates.""" | |
return self.__mul__(factor) | |
def __neg__(self): | |
"""Negate the point.""" | |
coords = [-x for x in self.args] | |
return Point(coords, evaluate=False) | |
def __sub__(self, other): | |
"""Subtract two points, or subtract a factor from this point's | |
coordinates.""" | |
return self + [-x for x in other] | |
def _normalize_dimension(cls, *points, **kwargs): | |
"""Ensure that points have the same dimension. | |
By default `on_morph='warn'` is passed to the | |
`Point` constructor.""" | |
# if we have a built-in ambient dimension, use it | |
dim = getattr(cls, '_ambient_dimension', None) | |
# override if we specified it | |
dim = kwargs.get('dim', dim) | |
# if no dim was given, use the highest dimensional point | |
if dim is None: | |
dim = max(i.ambient_dimension for i in points) | |
if all(i.ambient_dimension == dim for i in points): | |
return list(points) | |
kwargs['dim'] = dim | |
kwargs['on_morph'] = kwargs.get('on_morph', 'warn') | |
return [Point(i, **kwargs) for i in points] | |
def affine_rank(*args): | |
"""The affine rank of a set of points is the dimension | |
of the smallest affine space containing all the points. | |
For example, if the points lie on a line (and are not all | |
the same) their affine rank is 1. If the points lie on a plane | |
but not a line, their affine rank is 2. By convention, the empty | |
set has affine rank -1.""" | |
if len(args) == 0: | |
return -1 | |
# make sure we're genuinely points | |
# and translate every point to the origin | |
points = Point._normalize_dimension(*[Point(i) for i in args]) | |
origin = points[0] | |
points = [i - origin for i in points[1:]] | |
m = Matrix([i.args for i in points]) | |
# XXX fragile -- what is a better way? | |
return m.rank(iszerofunc = lambda x: | |
abs(x.n(2)) < 1e-12 if x.is_number else x.is_zero) | |
def ambient_dimension(self): | |
"""Number of components this point has.""" | |
return getattr(self, '_ambient_dimension', len(self)) | |
def are_coplanar(cls, *points): | |
"""Return True if there exists a plane in which all the points | |
lie. A trivial True value is returned if `len(points) < 3` or | |
all Points are 2-dimensional. | |
Parameters | |
========== | |
A set of points | |
Raises | |
====== | |
ValueError : if less than 3 unique points are given | |
Returns | |
======= | |
boolean | |
Examples | |
======== | |
>>> from sympy import Point3D | |
>>> p1 = Point3D(1, 2, 2) | |
>>> p2 = Point3D(2, 7, 2) | |
>>> p3 = Point3D(0, 0, 2) | |
>>> p4 = Point3D(1, 1, 2) | |
>>> Point3D.are_coplanar(p1, p2, p3, p4) | |
True | |
>>> p5 = Point3D(0, 1, 3) | |
>>> Point3D.are_coplanar(p1, p2, p3, p5) | |
False | |
""" | |
if len(points) <= 1: | |
return True | |
points = cls._normalize_dimension(*[Point(i) for i in points]) | |
# quick exit if we are in 2D | |
if points[0].ambient_dimension == 2: | |
return True | |
points = list(uniq(points)) | |
return Point.affine_rank(*points) <= 2 | |
def distance(self, other): | |
"""The Euclidean distance between self and another GeometricEntity. | |
Returns | |
======= | |
distance : number or symbolic expression. | |
Raises | |
====== | |
TypeError : if other is not recognized as a GeometricEntity or is a | |
GeometricEntity for which distance is not defined. | |
See Also | |
======== | |
sympy.geometry.line.Segment.length | |
sympy.geometry.point.Point.taxicab_distance | |
Examples | |
======== | |
>>> from sympy import Point, Line | |
>>> p1, p2 = Point(1, 1), Point(4, 5) | |
>>> l = Line((3, 1), (2, 2)) | |
>>> p1.distance(p2) | |
5 | |
>>> p1.distance(l) | |
sqrt(2) | |
The computed distance may be symbolic, too: | |
>>> from sympy.abc import x, y | |
>>> p3 = Point(x, y) | |
>>> p3.distance((0, 0)) | |
sqrt(x**2 + y**2) | |
""" | |
if not isinstance(other, GeometryEntity): | |
try: | |
other = Point(other, dim=self.ambient_dimension) | |
except TypeError: | |
raise TypeError("not recognized as a GeometricEntity: %s" % type(other)) | |
if isinstance(other, Point): | |
s, p = Point._normalize_dimension(self, Point(other)) | |
return sqrt(Add(*((a - b)**2 for a, b in zip(s, p)))) | |
distance = getattr(other, 'distance', None) | |
if distance is None: | |
raise TypeError("distance between Point and %s is not defined" % type(other)) | |
return distance(self) | |
def dot(self, p): | |
"""Return dot product of self with another Point.""" | |
if not is_sequence(p): | |
p = Point(p) # raise the error via Point | |
return Add(*(a*b for a, b in zip(self, p))) | |
def equals(self, other): | |
"""Returns whether the coordinates of self and other agree.""" | |
# a point is equal to another point if all its components are equal | |
if not isinstance(other, Point) or len(self) != len(other): | |
return False | |
return all(a.equals(b) for a, b in zip(self, other)) | |
def _eval_evalf(self, prec=15, **options): | |
"""Evaluate the coordinates of the point. | |
This method will, where possible, create and return a new Point | |
where the coordinates are evaluated as floating point numbers to | |
the precision indicated (default=15). | |
Parameters | |
========== | |
prec : int | |
Returns | |
======= | |
point : Point | |
Examples | |
======== | |
>>> from sympy import Point, Rational | |
>>> p1 = Point(Rational(1, 2), Rational(3, 2)) | |
>>> p1 | |
Point2D(1/2, 3/2) | |
>>> p1.evalf() | |
Point2D(0.5, 1.5) | |
""" | |
dps = prec_to_dps(prec) | |
coords = [x.evalf(n=dps, **options) for x in self.args] | |
return Point(*coords, evaluate=False) | |
def intersection(self, other): | |
"""The intersection between this point and another GeometryEntity. | |
Parameters | |
========== | |
other : GeometryEntity or sequence of coordinates | |
Returns | |
======= | |
intersection : list of Points | |
Notes | |
===== | |
The return value will either be an empty list if there is no | |
intersection, otherwise it will contain this point. | |
Examples | |
======== | |
>>> from sympy import Point | |
>>> p1, p2, p3 = Point(0, 0), Point(1, 1), Point(0, 0) | |
>>> p1.intersection(p2) | |
[] | |
>>> p1.intersection(p3) | |
[Point2D(0, 0)] | |
""" | |
if not isinstance(other, GeometryEntity): | |
other = Point(other) | |
if isinstance(other, Point): | |
if self == other: | |
return [self] | |
p1, p2 = Point._normalize_dimension(self, other) | |
if p1 == self and p1 == p2: | |
return [self] | |
return [] | |
return other.intersection(self) | |
def is_collinear(self, *args): | |
"""Returns `True` if there exists a line | |
that contains `self` and `points`. Returns `False` otherwise. | |
A trivially True value is returned if no points are given. | |
Parameters | |
========== | |
args : sequence of Points | |
Returns | |
======= | |
is_collinear : boolean | |
See Also | |
======== | |
sympy.geometry.line.Line | |
Examples | |
======== | |
>>> from sympy import Point | |
>>> from sympy.abc import x | |
>>> p1, p2 = Point(0, 0), Point(1, 1) | |
>>> p3, p4, p5 = Point(2, 2), Point(x, x), Point(1, 2) | |
>>> Point.is_collinear(p1, p2, p3, p4) | |
True | |
>>> Point.is_collinear(p1, p2, p3, p5) | |
False | |
""" | |
points = (self,) + args | |
points = Point._normalize_dimension(*[Point(i) for i in points]) | |
points = list(uniq(points)) | |
return Point.affine_rank(*points) <= 1 | |
def is_concyclic(self, *args): | |
"""Do `self` and the given sequence of points lie in a circle? | |
Returns True if the set of points are concyclic and | |
False otherwise. A trivial value of True is returned | |
if there are fewer than 2 other points. | |
Parameters | |
========== | |
args : sequence of Points | |
Returns | |
======= | |
is_concyclic : boolean | |
Examples | |
======== | |
>>> from sympy import Point | |
Define 4 points that are on the unit circle: | |
>>> p1, p2, p3, p4 = Point(1, 0), (0, 1), (-1, 0), (0, -1) | |
>>> p1.is_concyclic() == p1.is_concyclic(p2, p3, p4) == True | |
True | |
Define a point not on that circle: | |
>>> p = Point(1, 1) | |
>>> p.is_concyclic(p1, p2, p3) | |
False | |
""" | |
points = (self,) + args | |
points = Point._normalize_dimension(*[Point(i) for i in points]) | |
points = list(uniq(points)) | |
if not Point.affine_rank(*points) <= 2: | |
return False | |
origin = points[0] | |
points = [p - origin for p in points] | |
# points are concyclic if they are coplanar and | |
# there is a point c so that ||p_i-c|| == ||p_j-c|| for all | |
# i and j. Rearranging this equation gives us the following | |
# condition: the matrix `mat` must not a pivot in the last | |
# column. | |
mat = Matrix([list(i) + [i.dot(i)] for i in points]) | |
rref, pivots = mat.rref() | |
if len(origin) not in pivots: | |
return True | |
return False | |
def is_nonzero(self): | |
"""True if any coordinate is nonzero, False if every coordinate is zero, | |
and None if it cannot be determined.""" | |
is_zero = self.is_zero | |
if is_zero is None: | |
return None | |
return not is_zero | |
def is_scalar_multiple(self, p): | |
"""Returns whether each coordinate of `self` is a scalar | |
multiple of the corresponding coordinate in point p. | |
""" | |
s, o = Point._normalize_dimension(self, Point(p)) | |
# 2d points happen a lot, so optimize this function call | |
if s.ambient_dimension == 2: | |
(x1, y1), (x2, y2) = s.args, o.args | |
rv = (x1*y2 - x2*y1).equals(0) | |
if rv is None: | |
raise Undecidable(filldedent( | |
'''Cannot determine if %s is a scalar multiple of | |
%s''' % (s, o))) | |
# if the vectors p1 and p2 are linearly dependent, then they must | |
# be scalar multiples of each other | |
m = Matrix([s.args, o.args]) | |
return m.rank() < 2 | |
def is_zero(self): | |
"""True if every coordinate is zero, False if any coordinate is not zero, | |
and None if it cannot be determined.""" | |
nonzero = [x.is_nonzero for x in self.args] | |
if any(nonzero): | |
return False | |
if any(x is None for x in nonzero): | |
return None | |
return True | |
def length(self): | |
""" | |
Treating a Point as a Line, this returns 0 for the length of a Point. | |
Examples | |
======== | |
>>> from sympy import Point | |
>>> p = Point(0, 1) | |
>>> p.length | |
0 | |
""" | |
return S.Zero | |
def midpoint(self, p): | |
"""The midpoint between self and point p. | |
Parameters | |
========== | |
p : Point | |
Returns | |
======= | |
midpoint : Point | |
See Also | |
======== | |
sympy.geometry.line.Segment.midpoint | |
Examples | |
======== | |
>>> from sympy import Point | |
>>> p1, p2 = Point(1, 1), Point(13, 5) | |
>>> p1.midpoint(p2) | |
Point2D(7, 3) | |
""" | |
s, p = Point._normalize_dimension(self, Point(p)) | |
return Point([simplify((a + b)*S.Half) for a, b in zip(s, p)]) | |
def origin(self): | |
"""A point of all zeros of the same ambient dimension | |
as the current point""" | |
return Point([0]*len(self), evaluate=False) | |
def orthogonal_direction(self): | |
"""Returns a non-zero point that is orthogonal to the | |
line containing `self` and the origin. | |
Examples | |
======== | |
>>> from sympy import Line, Point | |
>>> a = Point(1, 2, 3) | |
>>> a.orthogonal_direction | |
Point3D(-2, 1, 0) | |
>>> b = _ | |
>>> Line(b, b.origin).is_perpendicular(Line(a, a.origin)) | |
True | |
""" | |
dim = self.ambient_dimension | |
# if a coordinate is zero, we can put a 1 there and zeros elsewhere | |
if self[0].is_zero: | |
return Point([1] + (dim - 1)*[0]) | |
if self[1].is_zero: | |
return Point([0,1] + (dim - 2)*[0]) | |
# if the first two coordinates aren't zero, we can create a non-zero | |
# orthogonal vector by swapping them, negating one, and padding with zeros | |
return Point([-self[1], self[0]] + (dim - 2)*[0]) | |
def project(a, b): | |
"""Project the point `a` onto the line between the origin | |
and point `b` along the normal direction. | |
Parameters | |
========== | |
a : Point | |
b : Point | |
Returns | |
======= | |
p : Point | |
See Also | |
======== | |
sympy.geometry.line.LinearEntity.projection | |
Examples | |
======== | |
>>> from sympy import Line, Point | |
>>> a = Point(1, 2) | |
>>> b = Point(2, 5) | |
>>> z = a.origin | |
>>> p = Point.project(a, b) | |
>>> Line(p, a).is_perpendicular(Line(p, b)) | |
True | |
>>> Point.is_collinear(z, p, b) | |
True | |
""" | |
a, b = Point._normalize_dimension(Point(a), Point(b)) | |
if b.is_zero: | |
raise ValueError("Cannot project to the zero vector.") | |
return b*(a.dot(b) / b.dot(b)) | |
def taxicab_distance(self, p): | |
"""The Taxicab Distance from self to point p. | |
Returns the sum of the horizontal and vertical distances to point p. | |
Parameters | |
========== | |
p : Point | |
Returns | |
======= | |
taxicab_distance : The sum of the horizontal | |
and vertical distances to point p. | |
See Also | |
======== | |
sympy.geometry.point.Point.distance | |
Examples | |
======== | |
>>> from sympy import Point | |
>>> p1, p2 = Point(1, 1), Point(4, 5) | |
>>> p1.taxicab_distance(p2) | |
7 | |
""" | |
s, p = Point._normalize_dimension(self, Point(p)) | |
return Add(*(abs(a - b) for a, b in zip(s, p))) | |
def canberra_distance(self, p): | |
"""The Canberra Distance from self to point p. | |
Returns the weighted sum of horizontal and vertical distances to | |
point p. | |
Parameters | |
========== | |
p : Point | |
Returns | |
======= | |
canberra_distance : The weighted sum of horizontal and vertical | |
distances to point p. The weight used is the sum of absolute values | |
of the coordinates. | |
Examples | |
======== | |
>>> from sympy import Point | |
>>> p1, p2 = Point(1, 1), Point(3, 3) | |
>>> p1.canberra_distance(p2) | |
1 | |
>>> p1, p2 = Point(0, 0), Point(3, 3) | |
>>> p1.canberra_distance(p2) | |
2 | |
Raises | |
====== | |
ValueError when both vectors are zero. | |
See Also | |
======== | |
sympy.geometry.point.Point.distance | |
""" | |
s, p = Point._normalize_dimension(self, Point(p)) | |
if self.is_zero and p.is_zero: | |
raise ValueError("Cannot project to the zero vector.") | |
return Add(*((abs(a - b)/(abs(a) + abs(b))) for a, b in zip(s, p))) | |
def unit(self): | |
"""Return the Point that is in the same direction as `self` | |
and a distance of 1 from the origin""" | |
return self / abs(self) | |
class Point2D(Point): | |
"""A point in a 2-dimensional Euclidean space. | |
Parameters | |
========== | |
coords | |
A sequence of 2 coordinate values. | |
Attributes | |
========== | |
x | |
y | |
length | |
Raises | |
====== | |
TypeError | |
When trying to add or subtract points with different dimensions. | |
When trying to create a point with more than two dimensions. | |
When `intersection` is called with object other than a Point. | |
See Also | |
======== | |
sympy.geometry.line.Segment : Connects two Points | |
Examples | |
======== | |
>>> from sympy import Point2D | |
>>> from sympy.abc import x | |
>>> Point2D(1, 2) | |
Point2D(1, 2) | |
>>> Point2D([1, 2]) | |
Point2D(1, 2) | |
>>> Point2D(0, x) | |
Point2D(0, x) | |
Floats are automatically converted to Rational unless the | |
evaluate flag is False: | |
>>> Point2D(0.5, 0.25) | |
Point2D(1/2, 1/4) | |
>>> Point2D(0.5, 0.25, evaluate=False) | |
Point2D(0.5, 0.25) | |
""" | |
_ambient_dimension = 2 | |
def __new__(cls, *args, _nocheck=False, **kwargs): | |
if not _nocheck: | |
kwargs['dim'] = 2 | |
args = Point(*args, **kwargs) | |
return GeometryEntity.__new__(cls, *args) | |
def __contains__(self, item): | |
return item == self | |
def bounds(self): | |
"""Return a tuple (xmin, ymin, xmax, ymax) representing the bounding | |
rectangle for the geometric figure. | |
""" | |
return (self.x, self.y, self.x, self.y) | |
def rotate(self, angle, pt=None): | |
"""Rotate ``angle`` radians counterclockwise about Point ``pt``. | |
See Also | |
======== | |
translate, scale | |
Examples | |
======== | |
>>> from sympy import Point2D, pi | |
>>> t = Point2D(1, 0) | |
>>> t.rotate(pi/2) | |
Point2D(0, 1) | |
>>> t.rotate(pi/2, (2, 0)) | |
Point2D(2, -1) | |
""" | |
c = cos(angle) | |
s = sin(angle) | |
rv = self | |
if pt is not None: | |
pt = Point(pt, dim=2) | |
rv -= pt | |
x, y = rv.args | |
rv = Point(c*x - s*y, s*x + c*y) | |
if pt is not None: | |
rv += pt | |
return rv | |
def scale(self, x=1, y=1, pt=None): | |
"""Scale the coordinates of the Point by multiplying by | |
``x`` and ``y`` after subtracting ``pt`` -- default is (0, 0) -- | |
and then adding ``pt`` back again (i.e. ``pt`` is the point of | |
reference for the scaling). | |
See Also | |
======== | |
rotate, translate | |
Examples | |
======== | |
>>> from sympy import Point2D | |
>>> t = Point2D(1, 1) | |
>>> t.scale(2) | |
Point2D(2, 1) | |
>>> t.scale(2, 2) | |
Point2D(2, 2) | |
""" | |
if pt: | |
pt = Point(pt, dim=2) | |
return self.translate(*(-pt).args).scale(x, y).translate(*pt.args) | |
return Point(self.x*x, self.y*y) | |
def transform(self, matrix): | |
"""Return the point after applying the transformation described | |
by the 3x3 Matrix, ``matrix``. | |
See Also | |
======== | |
sympy.geometry.point.Point2D.rotate | |
sympy.geometry.point.Point2D.scale | |
sympy.geometry.point.Point2D.translate | |
""" | |
if not (matrix.is_Matrix and matrix.shape == (3, 3)): | |
raise ValueError("matrix must be a 3x3 matrix") | |
x, y = self.args | |
return Point(*(Matrix(1, 3, [x, y, 1])*matrix).tolist()[0][:2]) | |
def translate(self, x=0, y=0): | |
"""Shift the Point by adding x and y to the coordinates of the Point. | |
See Also | |
======== | |
sympy.geometry.point.Point2D.rotate, scale | |
Examples | |
======== | |
>>> from sympy import Point2D | |
>>> t = Point2D(0, 1) | |
>>> t.translate(2) | |
Point2D(2, 1) | |
>>> t.translate(2, 2) | |
Point2D(2, 3) | |
>>> t + Point2D(2, 2) | |
Point2D(2, 3) | |
""" | |
return Point(self.x + x, self.y + y) | |
def coordinates(self): | |
""" | |
Returns the two coordinates of the Point. | |
Examples | |
======== | |
>>> from sympy import Point2D | |
>>> p = Point2D(0, 1) | |
>>> p.coordinates | |
(0, 1) | |
""" | |
return self.args | |
def x(self): | |
""" | |
Returns the X coordinate of the Point. | |
Examples | |
======== | |
>>> from sympy import Point2D | |
>>> p = Point2D(0, 1) | |
>>> p.x | |
0 | |
""" | |
return self.args[0] | |
def y(self): | |
""" | |
Returns the Y coordinate of the Point. | |
Examples | |
======== | |
>>> from sympy import Point2D | |
>>> p = Point2D(0, 1) | |
>>> p.y | |
1 | |
""" | |
return self.args[1] | |
class Point3D(Point): | |
"""A point in a 3-dimensional Euclidean space. | |
Parameters | |
========== | |
coords | |
A sequence of 3 coordinate values. | |
Attributes | |
========== | |
x | |
y | |
z | |
length | |
Raises | |
====== | |
TypeError | |
When trying to add or subtract points with different dimensions. | |
When `intersection` is called with object other than a Point. | |
Examples | |
======== | |
>>> from sympy import Point3D | |
>>> from sympy.abc import x | |
>>> Point3D(1, 2, 3) | |
Point3D(1, 2, 3) | |
>>> Point3D([1, 2, 3]) | |
Point3D(1, 2, 3) | |
>>> Point3D(0, x, 3) | |
Point3D(0, x, 3) | |
Floats are automatically converted to Rational unless the | |
evaluate flag is False: | |
>>> Point3D(0.5, 0.25, 2) | |
Point3D(1/2, 1/4, 2) | |
>>> Point3D(0.5, 0.25, 3, evaluate=False) | |
Point3D(0.5, 0.25, 3) | |
""" | |
_ambient_dimension = 3 | |
def __new__(cls, *args, _nocheck=False, **kwargs): | |
if not _nocheck: | |
kwargs['dim'] = 3 | |
args = Point(*args, **kwargs) | |
return GeometryEntity.__new__(cls, *args) | |
def __contains__(self, item): | |
return item == self | |
def are_collinear(*points): | |
"""Is a sequence of points collinear? | |
Test whether or not a set of points are collinear. Returns True if | |
the set of points are collinear, or False otherwise. | |
Parameters | |
========== | |
points : sequence of Point | |
Returns | |
======= | |
are_collinear : boolean | |
See Also | |
======== | |
sympy.geometry.line.Line3D | |
Examples | |
======== | |
>>> from sympy import Point3D | |
>>> from sympy.abc import x | |
>>> p1, p2 = Point3D(0, 0, 0), Point3D(1, 1, 1) | |
>>> p3, p4, p5 = Point3D(2, 2, 2), Point3D(x, x, x), Point3D(1, 2, 6) | |
>>> Point3D.are_collinear(p1, p2, p3, p4) | |
True | |
>>> Point3D.are_collinear(p1, p2, p3, p5) | |
False | |
""" | |
return Point.is_collinear(*points) | |
def direction_cosine(self, point): | |
""" | |
Gives the direction cosine between 2 points | |
Parameters | |
========== | |
p : Point3D | |
Returns | |
======= | |
list | |
Examples | |
======== | |
>>> from sympy import Point3D | |
>>> p1 = Point3D(1, 2, 3) | |
>>> p1.direction_cosine(Point3D(2, 3, 5)) | |
[sqrt(6)/6, sqrt(6)/6, sqrt(6)/3] | |
""" | |
a = self.direction_ratio(point) | |
b = sqrt(Add(*(i**2 for i in a))) | |
return [(point.x - self.x) / b,(point.y - self.y) / b, | |
(point.z - self.z) / b] | |
def direction_ratio(self, point): | |
""" | |
Gives the direction ratio between 2 points | |
Parameters | |
========== | |
p : Point3D | |
Returns | |
======= | |
list | |
Examples | |
======== | |
>>> from sympy import Point3D | |
>>> p1 = Point3D(1, 2, 3) | |
>>> p1.direction_ratio(Point3D(2, 3, 5)) | |
[1, 1, 2] | |
""" | |
return [(point.x - self.x),(point.y - self.y),(point.z - self.z)] | |
def intersection(self, other): | |
"""The intersection between this point and another GeometryEntity. | |
Parameters | |
========== | |
other : GeometryEntity or sequence of coordinates | |
Returns | |
======= | |
intersection : list of Points | |
Notes | |
===== | |
The return value will either be an empty list if there is no | |
intersection, otherwise it will contain this point. | |
Examples | |
======== | |
>>> from sympy import Point3D | |
>>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(1, 1, 1), Point3D(0, 0, 0) | |
>>> p1.intersection(p2) | |
[] | |
>>> p1.intersection(p3) | |
[Point3D(0, 0, 0)] | |
""" | |
if not isinstance(other, GeometryEntity): | |
other = Point(other, dim=3) | |
if isinstance(other, Point3D): | |
if self == other: | |
return [self] | |
return [] | |
return other.intersection(self) | |
def scale(self, x=1, y=1, z=1, pt=None): | |
"""Scale the coordinates of the Point by multiplying by | |
``x`` and ``y`` after subtracting ``pt`` -- default is (0, 0) -- | |
and then adding ``pt`` back again (i.e. ``pt`` is the point of | |
reference for the scaling). | |
See Also | |
======== | |
translate | |
Examples | |
======== | |
>>> from sympy import Point3D | |
>>> t = Point3D(1, 1, 1) | |
>>> t.scale(2) | |
Point3D(2, 1, 1) | |
>>> t.scale(2, 2) | |
Point3D(2, 2, 1) | |
""" | |
if pt: | |
pt = Point3D(pt) | |
return self.translate(*(-pt).args).scale(x, y, z).translate(*pt.args) | |
return Point3D(self.x*x, self.y*y, self.z*z) | |
def transform(self, matrix): | |
"""Return the point after applying the transformation described | |
by the 4x4 Matrix, ``matrix``. | |
See Also | |
======== | |
sympy.geometry.point.Point3D.scale | |
sympy.geometry.point.Point3D.translate | |
""" | |
if not (matrix.is_Matrix and matrix.shape == (4, 4)): | |
raise ValueError("matrix must be a 4x4 matrix") | |
x, y, z = self.args | |
m = Transpose(matrix) | |
return Point3D(*(Matrix(1, 4, [x, y, z, 1])*m).tolist()[0][:3]) | |
def translate(self, x=0, y=0, z=0): | |
"""Shift the Point by adding x and y to the coordinates of the Point. | |
See Also | |
======== | |
scale | |
Examples | |
======== | |
>>> from sympy import Point3D | |
>>> t = Point3D(0, 1, 1) | |
>>> t.translate(2) | |
Point3D(2, 1, 1) | |
>>> t.translate(2, 2) | |
Point3D(2, 3, 1) | |
>>> t + Point3D(2, 2, 2) | |
Point3D(2, 3, 3) | |
""" | |
return Point3D(self.x + x, self.y + y, self.z + z) | |
def coordinates(self): | |
""" | |
Returns the three coordinates of the Point. | |
Examples | |
======== | |
>>> from sympy import Point3D | |
>>> p = Point3D(0, 1, 2) | |
>>> p.coordinates | |
(0, 1, 2) | |
""" | |
return self.args | |
def x(self): | |
""" | |
Returns the X coordinate of the Point. | |
Examples | |
======== | |
>>> from sympy import Point3D | |
>>> p = Point3D(0, 1, 3) | |
>>> p.x | |
0 | |
""" | |
return self.args[0] | |
def y(self): | |
""" | |
Returns the Y coordinate of the Point. | |
Examples | |
======== | |
>>> from sympy import Point3D | |
>>> p = Point3D(0, 1, 2) | |
>>> p.y | |
1 | |
""" | |
return self.args[1] | |
def z(self): | |
""" | |
Returns the Z coordinate of the Point. | |
Examples | |
======== | |
>>> from sympy import Point3D | |
>>> p = Point3D(0, 1, 1) | |
>>> p.z | |
1 | |
""" | |
return self.args[2] | |