|
"""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.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') |
|
|
|
|
|
coords = args[0] if len(args) == 1 else args |
|
|
|
|
|
if isinstance(coords, Point): |
|
|
|
|
|
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)))) |
|
|
|
|
|
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.') |
|
|
|
|
|
coords = coords[:dim] + (S.Zero,)*(dim - len(coords)) |
|
|
|
|
|
|
|
if evaluate: |
|
coords = coords.xreplace({ |
|
f: simplify(nsimplify(f, rational=True)) |
|
for f in coords.atoms(Float)}) |
|
|
|
|
|
if len(coords) == 2: |
|
kwargs['_nocheck'] = True |
|
return Point2D(*coords, **kwargs) |
|
elif len(coords) == 3: |
|
kwargs['_nocheck'] = True |
|
return Point3D(*coords, **kwargs) |
|
|
|
|
|
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] |
|
|
|
@classmethod |
|
def _normalize_dimension(cls, *points, **kwargs): |
|
"""Ensure that points have the same dimension. |
|
By default `on_morph='warn'` is passed to the |
|
`Point` constructor.""" |
|
|
|
dim = getattr(cls, '_ambient_dimension', None) |
|
|
|
dim = kwargs.get('dim', dim) |
|
|
|
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] |
|
|
|
@staticmethod |
|
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 |
|
|
|
|
|
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]) |
|
|
|
return m.rank(iszerofunc = lambda x: |
|
abs(x.n(2)) < 1e-12 if x.is_number else x.is_zero) |
|
|
|
@property |
|
def ambient_dimension(self): |
|
"""Number of components this point has.""" |
|
return getattr(self, '_ambient_dimension', len(self)) |
|
|
|
@classmethod |
|
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]) |
|
|
|
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) |
|
return Add(*(a*b for a, b in zip(self, p))) |
|
|
|
def equals(self, other): |
|
"""Returns whether the coordinates of self and other agree.""" |
|
|
|
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] |
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
|
@property |
|
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)) |
|
|
|
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))) |
|
|
|
|
|
|
|
m = Matrix([s.args, o.args]) |
|
return m.rank() < 2 |
|
|
|
@property |
|
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 |
|
|
|
@property |
|
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)]) |
|
|
|
@property |
|
def origin(self): |
|
"""A point of all zeros of the same ambient dimension |
|
as the current point""" |
|
return Point([0]*len(self), evaluate=False) |
|
|
|
@property |
|
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 self[0].is_zero: |
|
return Point([1] + (dim - 1)*[0]) |
|
if self[1].is_zero: |
|
return Point([0,1] + (dim - 2)*[0]) |
|
|
|
|
|
return Point([-self[1], self[0]] + (dim - 2)*[0]) |
|
|
|
@staticmethod |
|
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))) |
|
|
|
@property |
|
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 |
|
|
|
@property |
|
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) |
|
|
|
@property |
|
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 |
|
|
|
@property |
|
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] |
|
|
|
@property |
|
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 |
|
|
|
@staticmethod |
|
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) |
|
|
|
@property |
|
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 |
|
|
|
@property |
|
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] |
|
|
|
@property |
|
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] |
|
|
|
@property |
|
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] |
|
|