chore: 添加虚拟环境到仓库
- 添加 backend_service/venv 虚拟环境 - 包含所有Python依赖包 - 注意:虚拟环境约393MB,包含12655个文件
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
"""
|
||||
A geometry module for the SymPy library. This module contains all of the
|
||||
entities and functions needed to construct basic geometrical data and to
|
||||
perform simple informational queries.
|
||||
|
||||
Usage:
|
||||
======
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
"""
|
||||
from sympy.geometry.point import Point, Point2D, Point3D
|
||||
from sympy.geometry.line import Line, Ray, Segment, Line2D, Segment2D, Ray2D, \
|
||||
Line3D, Segment3D, Ray3D
|
||||
from sympy.geometry.plane import Plane
|
||||
from sympy.geometry.ellipse import Ellipse, Circle
|
||||
from sympy.geometry.polygon import Polygon, RegularPolygon, Triangle, rad, deg
|
||||
from sympy.geometry.util import are_similar, centroid, convex_hull, idiff, \
|
||||
intersection, closest_points, farthest_points
|
||||
from sympy.geometry.exceptions import GeometryError
|
||||
from sympy.geometry.curve import Curve
|
||||
from sympy.geometry.parabola import Parabola
|
||||
|
||||
__all__ = [
|
||||
'Point', 'Point2D', 'Point3D',
|
||||
|
||||
'Line', 'Ray', 'Segment', 'Line2D', 'Segment2D', 'Ray2D', 'Line3D',
|
||||
'Segment3D', 'Ray3D',
|
||||
|
||||
'Plane',
|
||||
|
||||
'Ellipse', 'Circle',
|
||||
|
||||
'Polygon', 'RegularPolygon', 'Triangle', 'rad', 'deg',
|
||||
|
||||
'are_similar', 'centroid', 'convex_hull', 'idiff', 'intersection',
|
||||
'closest_points', 'farthest_points',
|
||||
|
||||
'GeometryError',
|
||||
|
||||
'Curve',
|
||||
|
||||
'Parabola',
|
||||
]
|
||||
@@ -0,0 +1,424 @@
|
||||
"""Curves in 2-dimensional Euclidean space.
|
||||
|
||||
Contains
|
||||
========
|
||||
Curve
|
||||
|
||||
"""
|
||||
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.core import diff
|
||||
from sympy.core.containers import Tuple
|
||||
from sympy.core.symbol import _symbol
|
||||
from sympy.geometry.entity import GeometryEntity, GeometrySet
|
||||
from sympy.geometry.point import Point
|
||||
from sympy.integrals import integrate
|
||||
from sympy.matrices import Matrix, rot_axis3
|
||||
from sympy.utilities.iterables import is_sequence
|
||||
|
||||
from mpmath.libmp.libmpf import prec_to_dps
|
||||
|
||||
|
||||
class Curve(GeometrySet):
|
||||
"""A curve in space.
|
||||
|
||||
A curve is defined by parametric functions for the coordinates, a
|
||||
parameter and the lower and upper bounds for the parameter value.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
function : list of functions
|
||||
limits : 3-tuple
|
||||
Function parameter and lower and upper bounds.
|
||||
|
||||
Attributes
|
||||
==========
|
||||
|
||||
functions
|
||||
parameter
|
||||
limits
|
||||
|
||||
Raises
|
||||
======
|
||||
|
||||
ValueError
|
||||
When `functions` are specified incorrectly.
|
||||
When `limits` are specified incorrectly.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Curve, sin, cos, interpolate
|
||||
>>> from sympy.abc import t, a
|
||||
>>> C = Curve((sin(t), cos(t)), (t, 0, 2))
|
||||
>>> C.functions
|
||||
(sin(t), cos(t))
|
||||
>>> C.limits
|
||||
(t, 0, 2)
|
||||
>>> C.parameter
|
||||
t
|
||||
>>> C = Curve((t, interpolate([1, 4, 9, 16], t)), (t, 0, 1)); C
|
||||
Curve((t, t**2), (t, 0, 1))
|
||||
>>> C.subs(t, 4)
|
||||
Point2D(4, 16)
|
||||
>>> C.arbitrary_point(a)
|
||||
Point2D(a, a**2)
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.core.function.Function
|
||||
sympy.polys.polyfuncs.interpolate
|
||||
|
||||
"""
|
||||
|
||||
def __new__(cls, function, limits):
|
||||
if not is_sequence(function) or len(function) != 2:
|
||||
raise ValueError("Function argument should be (x(t), y(t)) "
|
||||
"but got %s" % str(function))
|
||||
if not is_sequence(limits) or len(limits) != 3:
|
||||
raise ValueError("Limit argument should be (t, tmin, tmax) "
|
||||
"but got %s" % str(limits))
|
||||
|
||||
return GeometryEntity.__new__(cls, Tuple(*function), Tuple(*limits))
|
||||
|
||||
def __call__(self, f):
|
||||
return self.subs(self.parameter, f)
|
||||
|
||||
def _eval_subs(self, old, new):
|
||||
if old == self.parameter:
|
||||
return Point(*[f.subs(old, new) for f in self.functions])
|
||||
|
||||
def _eval_evalf(self, prec=15, **options):
|
||||
f, (t, a, b) = self.args
|
||||
dps = prec_to_dps(prec)
|
||||
f = tuple([i.evalf(n=dps, **options) for i in f])
|
||||
a, b = [i.evalf(n=dps, **options) for i in (a, b)]
|
||||
return self.func(f, (t, a, b))
|
||||
|
||||
def arbitrary_point(self, parameter='t'):
|
||||
"""A parameterized point on the curve.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
parameter : str or Symbol, optional
|
||||
Default value is 't'.
|
||||
The Curve's parameter is selected with None or self.parameter
|
||||
otherwise the provided symbol is used.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
Point :
|
||||
Returns a point in parametric form.
|
||||
|
||||
Raises
|
||||
======
|
||||
|
||||
ValueError
|
||||
When `parameter` already appears in the functions.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Curve, Symbol
|
||||
>>> from sympy.abc import s
|
||||
>>> C = Curve([2*s, s**2], (s, 0, 2))
|
||||
>>> C.arbitrary_point()
|
||||
Point2D(2*t, t**2)
|
||||
>>> C.arbitrary_point(C.parameter)
|
||||
Point2D(2*s, s**2)
|
||||
>>> C.arbitrary_point(None)
|
||||
Point2D(2*s, s**2)
|
||||
>>> C.arbitrary_point(Symbol('a'))
|
||||
Point2D(2*a, a**2)
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.geometry.point.Point
|
||||
|
||||
"""
|
||||
if parameter is None:
|
||||
return Point(*self.functions)
|
||||
|
||||
tnew = _symbol(parameter, self.parameter, real=True)
|
||||
t = self.parameter
|
||||
if (tnew.name != t.name and
|
||||
tnew.name in (f.name for f in self.free_symbols)):
|
||||
raise ValueError('Symbol %s already appears in object '
|
||||
'and cannot be used as a parameter.' % tnew.name)
|
||||
return Point(*[w.subs(t, tnew) for w in self.functions])
|
||||
|
||||
@property
|
||||
def free_symbols(self):
|
||||
"""Return a set of symbols other than the bound symbols used to
|
||||
parametrically define the Curve.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
set :
|
||||
Set of all non-parameterized symbols.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.abc import t, a
|
||||
>>> from sympy import Curve
|
||||
>>> Curve((t, t**2), (t, 0, 2)).free_symbols
|
||||
set()
|
||||
>>> Curve((t, t**2), (t, a, 2)).free_symbols
|
||||
{a}
|
||||
|
||||
"""
|
||||
free = set()
|
||||
for a in self.functions + self.limits[1:]:
|
||||
free |= a.free_symbols
|
||||
free = free.difference({self.parameter})
|
||||
return free
|
||||
|
||||
@property
|
||||
def ambient_dimension(self):
|
||||
"""The dimension of the curve.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
int :
|
||||
the dimension of curve.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.abc import t
|
||||
>>> from sympy import Curve
|
||||
>>> C = Curve((t, t**2), (t, 0, 2))
|
||||
>>> C.ambient_dimension
|
||||
2
|
||||
|
||||
"""
|
||||
|
||||
return len(self.args[0])
|
||||
|
||||
@property
|
||||
def functions(self):
|
||||
"""The functions specifying the curve.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
functions :
|
||||
list of parameterized coordinate functions.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.abc import t
|
||||
>>> from sympy import Curve
|
||||
>>> C = Curve((t, t**2), (t, 0, 2))
|
||||
>>> C.functions
|
||||
(t, t**2)
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
parameter
|
||||
|
||||
"""
|
||||
return self.args[0]
|
||||
|
||||
@property
|
||||
def limits(self):
|
||||
"""The limits for the curve.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
limits : tuple
|
||||
Contains parameter and lower and upper limits.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.abc import t
|
||||
>>> from sympy import Curve
|
||||
>>> C = Curve([t, t**3], (t, -2, 2))
|
||||
>>> C.limits
|
||||
(t, -2, 2)
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
plot_interval
|
||||
|
||||
"""
|
||||
return self.args[1]
|
||||
|
||||
@property
|
||||
def parameter(self):
|
||||
"""The curve function variable.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
Symbol :
|
||||
returns a bound symbol.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.abc import t
|
||||
>>> from sympy import Curve
|
||||
>>> C = Curve([t, t**2], (t, 0, 2))
|
||||
>>> C.parameter
|
||||
t
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
functions
|
||||
|
||||
"""
|
||||
return self.args[1][0]
|
||||
|
||||
@property
|
||||
def length(self):
|
||||
"""The curve length.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Curve
|
||||
>>> from sympy.abc import t
|
||||
>>> Curve((t, t), (t, 0, 1)).length
|
||||
sqrt(2)
|
||||
|
||||
"""
|
||||
integrand = sqrt(sum(diff(func, self.limits[0])**2 for func in self.functions))
|
||||
return integrate(integrand, self.limits)
|
||||
|
||||
def plot_interval(self, parameter='t'):
|
||||
"""The plot interval for the default geometric plot of the curve.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
parameter : str or Symbol, optional
|
||||
Default value is 't';
|
||||
otherwise the provided symbol is used.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
List :
|
||||
the plot interval as below:
|
||||
[parameter, lower_bound, upper_bound]
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Curve, sin
|
||||
>>> from sympy.abc import x, s
|
||||
>>> Curve((x, sin(x)), (x, 1, 2)).plot_interval()
|
||||
[t, 1, 2]
|
||||
>>> Curve((x, sin(x)), (x, 1, 2)).plot_interval(s)
|
||||
[s, 1, 2]
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
limits : Returns limits of the parameter interval
|
||||
|
||||
"""
|
||||
t = _symbol(parameter, self.parameter, real=True)
|
||||
return [t] + list(self.limits[1:])
|
||||
|
||||
def rotate(self, angle=0, pt=None):
|
||||
"""This function is used to rotate a curve along given point ``pt`` at given angle(in radian).
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
angle :
|
||||
the angle at which the curve will be rotated(in radian) in counterclockwise direction.
|
||||
default value of angle is 0.
|
||||
|
||||
pt : Point
|
||||
the point along which the curve will be rotated.
|
||||
If no point given, the curve will be rotated around origin.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
Curve :
|
||||
returns a curve rotated at given angle along given point.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Curve, pi
|
||||
>>> from sympy.abc import x
|
||||
>>> Curve((x, x), (x, 0, 1)).rotate(pi/2)
|
||||
Curve((-x, x), (x, 0, 1))
|
||||
|
||||
"""
|
||||
if pt:
|
||||
pt = -Point(pt, dim=2)
|
||||
else:
|
||||
pt = Point(0,0)
|
||||
rv = self.translate(*pt.args)
|
||||
f = list(rv.functions)
|
||||
f.append(0)
|
||||
f = Matrix(1, 3, f)
|
||||
f *= rot_axis3(angle)
|
||||
rv = self.func(f[0, :2].tolist()[0], self.limits)
|
||||
pt = -pt
|
||||
return rv.translate(*pt.args)
|
||||
|
||||
def scale(self, x=1, y=1, pt=None):
|
||||
"""Override GeometryEntity.scale since Curve is not made up of Points.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
Curve :
|
||||
returns scaled curve.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Curve
|
||||
>>> from sympy.abc import x
|
||||
>>> Curve((x, x), (x, 0, 1)).scale(2)
|
||||
Curve((2*x, x), (x, 0, 1))
|
||||
|
||||
"""
|
||||
if pt:
|
||||
pt = Point(pt, dim=2)
|
||||
return self.translate(*(-pt).args).scale(x, y).translate(*pt.args)
|
||||
fx, fy = self.functions
|
||||
return self.func((fx*x, fy*y), self.limits)
|
||||
|
||||
def translate(self, x=0, y=0):
|
||||
"""Translate the Curve by (x, y).
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
Curve :
|
||||
returns a translated curve.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Curve
|
||||
>>> from sympy.abc import x
|
||||
>>> Curve((x, x), (x, 0, 1)).translate(1, 2)
|
||||
Curve((x + 1, x + 2), (x, 0, 1))
|
||||
|
||||
"""
|
||||
fx, fy = self.functions
|
||||
return self.func((fx + x, fy + y), self.limits)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,641 @@
|
||||
"""The definition of the base geometrical entity with attributes common to
|
||||
all derived geometrical entities.
|
||||
|
||||
Contains
|
||||
========
|
||||
|
||||
GeometryEntity
|
||||
GeometricSet
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
A GeometryEntity is any object that has special geometric properties.
|
||||
A GeometrySet is a superclass of any GeometryEntity that can also
|
||||
be viewed as a sympy.sets.Set. In particular, points are the only
|
||||
GeometryEntity not considered a Set.
|
||||
|
||||
Rn is a GeometrySet representing n-dimensional Euclidean space. R2 and
|
||||
R3 are currently the only ambient spaces implemented.
|
||||
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from sympy.core.basic import Basic
|
||||
from sympy.core.containers import Tuple
|
||||
from sympy.core.evalf import EvalfMixin, N
|
||||
from sympy.core.numbers import oo
|
||||
from sympy.core.symbol import Dummy
|
||||
from sympy.core.sympify import sympify
|
||||
from sympy.functions.elementary.trigonometric import cos, sin, atan
|
||||
from sympy.matrices import eye
|
||||
from sympy.multipledispatch import dispatch
|
||||
from sympy.printing import sstr
|
||||
from sympy.sets import Set, Union, FiniteSet
|
||||
from sympy.sets.handlers.intersection import intersection_sets
|
||||
from sympy.sets.handlers.union import union_sets
|
||||
from sympy.solvers.solvers import solve
|
||||
from sympy.utilities.misc import func_name
|
||||
from sympy.utilities.iterables import is_sequence
|
||||
|
||||
|
||||
# How entities are ordered; used by __cmp__ in GeometryEntity
|
||||
ordering_of_classes = [
|
||||
"Point2D",
|
||||
"Point3D",
|
||||
"Point",
|
||||
"Segment2D",
|
||||
"Ray2D",
|
||||
"Line2D",
|
||||
"Segment3D",
|
||||
"Line3D",
|
||||
"Ray3D",
|
||||
"Segment",
|
||||
"Ray",
|
||||
"Line",
|
||||
"Plane",
|
||||
"Triangle",
|
||||
"RegularPolygon",
|
||||
"Polygon",
|
||||
"Circle",
|
||||
"Ellipse",
|
||||
"Curve",
|
||||
"Parabola"
|
||||
]
|
||||
|
||||
|
||||
x, y = [Dummy('entity_dummy') for i in range(2)]
|
||||
T = Dummy('entity_dummy', real=True)
|
||||
|
||||
|
||||
class GeometryEntity(Basic, EvalfMixin):
|
||||
"""The base class for all geometrical entities.
|
||||
|
||||
This class does not represent any particular geometric entity, it only
|
||||
provides the implementation of some methods common to all subclasses.
|
||||
|
||||
"""
|
||||
|
||||
__slots__: tuple[str, ...] = ()
|
||||
|
||||
def __cmp__(self, other):
|
||||
"""Comparison of two GeometryEntities."""
|
||||
n1 = self.__class__.__name__
|
||||
n2 = other.__class__.__name__
|
||||
c = (n1 > n2) - (n1 < n2)
|
||||
if not c:
|
||||
return 0
|
||||
|
||||
i1 = -1
|
||||
for cls in self.__class__.__mro__:
|
||||
try:
|
||||
i1 = ordering_of_classes.index(cls.__name__)
|
||||
break
|
||||
except ValueError:
|
||||
i1 = -1
|
||||
if i1 == -1:
|
||||
return c
|
||||
|
||||
i2 = -1
|
||||
for cls in other.__class__.__mro__:
|
||||
try:
|
||||
i2 = ordering_of_classes.index(cls.__name__)
|
||||
break
|
||||
except ValueError:
|
||||
i2 = -1
|
||||
if i2 == -1:
|
||||
return c
|
||||
|
||||
return (i1 > i2) - (i1 < i2)
|
||||
|
||||
def __contains__(self, other):
|
||||
"""Subclasses should implement this method for anything more complex than equality."""
|
||||
if type(self) is type(other):
|
||||
return self == other
|
||||
raise NotImplementedError()
|
||||
|
||||
def __getnewargs__(self):
|
||||
"""Returns a tuple that will be passed to __new__ on unpickling."""
|
||||
return tuple(self.args)
|
||||
|
||||
def __ne__(self, o):
|
||||
"""Test inequality of two geometrical entities."""
|
||||
return not self == o
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
# Points are sequences, but they should not
|
||||
# be converted to Tuples, so use this detection function instead.
|
||||
def is_seq_and_not_point(a):
|
||||
# we cannot use isinstance(a, Point) since we cannot import Point
|
||||
if hasattr(a, 'is_Point') and a.is_Point:
|
||||
return False
|
||||
return is_sequence(a)
|
||||
|
||||
args = [Tuple(*a) if is_seq_and_not_point(a) else sympify(a) for a in args]
|
||||
return Basic.__new__(cls, *args)
|
||||
|
||||
def __radd__(self, a):
|
||||
"""Implementation of reverse add method."""
|
||||
return a.__add__(self)
|
||||
|
||||
def __rtruediv__(self, a):
|
||||
"""Implementation of reverse division method."""
|
||||
return a.__truediv__(self)
|
||||
|
||||
def __repr__(self):
|
||||
"""String representation of a GeometryEntity that can be evaluated
|
||||
by sympy."""
|
||||
return type(self).__name__ + repr(self.args)
|
||||
|
||||
def __rmul__(self, a):
|
||||
"""Implementation of reverse multiplication method."""
|
||||
return a.__mul__(self)
|
||||
|
||||
def __rsub__(self, a):
|
||||
"""Implementation of reverse subtraction method."""
|
||||
return a.__sub__(self)
|
||||
|
||||
def __str__(self):
|
||||
"""String representation of a GeometryEntity."""
|
||||
return type(self).__name__ + sstr(self.args)
|
||||
|
||||
def _eval_subs(self, old, new):
|
||||
from sympy.geometry.point import Point, Point3D
|
||||
if is_sequence(old) or is_sequence(new):
|
||||
if isinstance(self, Point3D):
|
||||
old = Point3D(old)
|
||||
new = Point3D(new)
|
||||
else:
|
||||
old = Point(old)
|
||||
new = Point(new)
|
||||
return self._subs(old, new)
|
||||
|
||||
def _repr_svg_(self):
|
||||
"""SVG representation of a GeometryEntity suitable for IPython"""
|
||||
|
||||
try:
|
||||
bounds = self.bounds
|
||||
except (NotImplementedError, TypeError):
|
||||
# if we have no SVG representation, return None so IPython
|
||||
# will fall back to the next representation
|
||||
return None
|
||||
|
||||
if not all(x.is_number and x.is_finite for x in bounds):
|
||||
return None
|
||||
|
||||
svg_top = '''<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="{1}" height="{2}" viewBox="{0}"
|
||||
preserveAspectRatio="xMinYMin meet">
|
||||
<defs>
|
||||
<marker id="markerCircle" markerWidth="8" markerHeight="8"
|
||||
refx="5" refy="5" markerUnits="strokeWidth">
|
||||
<circle cx="5" cy="5" r="1.5" style="stroke: none; fill:#000000;"/>
|
||||
</marker>
|
||||
<marker id="markerArrow" markerWidth="13" markerHeight="13" refx="2" refy="4"
|
||||
orient="auto" markerUnits="strokeWidth">
|
||||
<path d="M2,2 L2,6 L6,4" style="fill: #000000;" />
|
||||
</marker>
|
||||
<marker id="markerReverseArrow" markerWidth="13" markerHeight="13" refx="6" refy="4"
|
||||
orient="auto" markerUnits="strokeWidth">
|
||||
<path d="M6,2 L6,6 L2,4" style="fill: #000000;" />
|
||||
</marker>
|
||||
</defs>'''
|
||||
|
||||
# Establish SVG canvas that will fit all the data + small space
|
||||
xmin, ymin, xmax, ymax = map(N, bounds)
|
||||
if xmin == xmax and ymin == ymax:
|
||||
# This is a point; buffer using an arbitrary size
|
||||
xmin, ymin, xmax, ymax = xmin - .5, ymin -.5, xmax + .5, ymax + .5
|
||||
else:
|
||||
# Expand bounds by a fraction of the data ranges
|
||||
expand = 0.1 # or 10%; this keeps arrowheads in view (R plots use 4%)
|
||||
widest_part = max([xmax - xmin, ymax - ymin])
|
||||
expand_amount = widest_part * expand
|
||||
xmin -= expand_amount
|
||||
ymin -= expand_amount
|
||||
xmax += expand_amount
|
||||
ymax += expand_amount
|
||||
dx = xmax - xmin
|
||||
dy = ymax - ymin
|
||||
width = min([max([100., dx]), 300])
|
||||
height = min([max([100., dy]), 300])
|
||||
|
||||
scale_factor = 1. if max(width, height) == 0 else max(dx, dy) / max(width, height)
|
||||
try:
|
||||
svg = self._svg(scale_factor)
|
||||
except (NotImplementedError, TypeError):
|
||||
# if we have no SVG representation, return None so IPython
|
||||
# will fall back to the next representation
|
||||
return None
|
||||
|
||||
view_box = "{} {} {} {}".format(xmin, ymin, dx, dy)
|
||||
transform = "matrix(1,0,0,-1,0,{})".format(ymax + ymin)
|
||||
svg_top = svg_top.format(view_box, width, height)
|
||||
|
||||
return svg_top + (
|
||||
'<g transform="{}">{}</g></svg>'
|
||||
).format(transform, svg)
|
||||
|
||||
def _svg(self, scale_factor=1., fill_color="#66cc99"):
|
||||
"""Returns SVG path element for the GeometryEntity.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
scale_factor : float
|
||||
Multiplication factor for the SVG stroke-width. Default is 1.
|
||||
fill_color : str, optional
|
||||
Hex string for fill color. Default is "#66cc99".
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def _sympy_(self):
|
||||
return self
|
||||
|
||||
@property
|
||||
def ambient_dimension(self):
|
||||
"""What is the dimension of the space that the object is contained in?"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def bounds(self):
|
||||
"""Return a tuple (xmin, ymin, xmax, ymax) representing the bounding
|
||||
rectangle for the geometric figure.
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def encloses(self, o):
|
||||
"""
|
||||
Return True if o is inside (not on or outside) the boundaries of self.
|
||||
|
||||
The object will be decomposed into Points and individual Entities need
|
||||
only define an encloses_point method for their class.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.geometry.ellipse.Ellipse.encloses_point
|
||||
sympy.geometry.polygon.Polygon.encloses_point
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import RegularPolygon, Point, Polygon
|
||||
>>> t = Polygon(*RegularPolygon(Point(0, 0), 1, 3).vertices)
|
||||
>>> t2 = Polygon(*RegularPolygon(Point(0, 0), 2, 3).vertices)
|
||||
>>> t2.encloses(t)
|
||||
True
|
||||
>>> t.encloses(t2)
|
||||
False
|
||||
|
||||
"""
|
||||
|
||||
from sympy.geometry.point import Point
|
||||
from sympy.geometry.line import Segment, Ray, Line
|
||||
from sympy.geometry.ellipse import Ellipse
|
||||
from sympy.geometry.polygon import Polygon, RegularPolygon
|
||||
|
||||
if isinstance(o, Point):
|
||||
return self.encloses_point(o)
|
||||
elif isinstance(o, Segment):
|
||||
return all(self.encloses_point(x) for x in o.points)
|
||||
elif isinstance(o, (Ray, Line)):
|
||||
return False
|
||||
elif isinstance(o, Ellipse):
|
||||
return self.encloses_point(o.center) and \
|
||||
self.encloses_point(
|
||||
Point(o.center.x + o.hradius, o.center.y)) and \
|
||||
not self.intersection(o)
|
||||
elif isinstance(o, Polygon):
|
||||
if isinstance(o, RegularPolygon):
|
||||
if not self.encloses_point(o.center):
|
||||
return False
|
||||
return all(self.encloses_point(v) for v in o.vertices)
|
||||
raise NotImplementedError()
|
||||
|
||||
def equals(self, o):
|
||||
return self == o
|
||||
|
||||
def intersection(self, o):
|
||||
"""
|
||||
Returns a list of all of the intersections of self with o.
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
An entity is not required to implement this method.
|
||||
|
||||
If two different types of entities can intersect, the item with
|
||||
higher index in ordering_of_classes should implement
|
||||
intersections with anything having a lower index.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.geometry.util.intersection
|
||||
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def is_similar(self, other):
|
||||
"""Is this geometrical entity similar to another geometrical entity?
|
||||
|
||||
Two entities are similar if a uniform scaling (enlarging or
|
||||
shrinking) of one of the entities will allow one to obtain the other.
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
This method is not intended to be used directly but rather
|
||||
through the `are_similar` function found in util.py.
|
||||
An entity is not required to implement this method.
|
||||
If two different types of entities can be similar, it is only
|
||||
required that one of them be able to determine this.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
scale
|
||||
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def reflect(self, line):
|
||||
"""
|
||||
Reflects an object across a line.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
line: Line
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import pi, sqrt, Line, RegularPolygon
|
||||
>>> l = Line((0, pi), slope=sqrt(2))
|
||||
>>> pent = RegularPolygon((1, 2), 1, 5)
|
||||
>>> rpent = pent.reflect(l)
|
||||
>>> rpent
|
||||
RegularPolygon(Point2D(-2*sqrt(2)*pi/3 - 1/3 + 4*sqrt(2)/3, 2/3 + 2*sqrt(2)/3 + 2*pi/3), -1, 5, -atan(2*sqrt(2)) + 3*pi/5)
|
||||
|
||||
>>> from sympy import pi, Line, Circle, Point
|
||||
>>> l = Line((0, pi), slope=1)
|
||||
>>> circ = Circle(Point(0, 0), 5)
|
||||
>>> rcirc = circ.reflect(l)
|
||||
>>> rcirc
|
||||
Circle(Point2D(-pi, pi), -5)
|
||||
|
||||
"""
|
||||
from sympy.geometry.point import Point
|
||||
|
||||
g = self
|
||||
l = line
|
||||
o = Point(0, 0)
|
||||
if l.slope.is_zero:
|
||||
v = l.args[0].y
|
||||
if not v: # x-axis
|
||||
return g.scale(y=-1)
|
||||
reps = [(p, p.translate(y=2*(v - p.y))) for p in g.atoms(Point)]
|
||||
elif l.slope is oo:
|
||||
v = l.args[0].x
|
||||
if not v: # y-axis
|
||||
return g.scale(x=-1)
|
||||
reps = [(p, p.translate(x=2*(v - p.x))) for p in g.atoms(Point)]
|
||||
else:
|
||||
if not hasattr(g, 'reflect') and not all(
|
||||
isinstance(arg, Point) for arg in g.args):
|
||||
raise NotImplementedError(
|
||||
'reflect undefined or non-Point args in %s' % g)
|
||||
a = atan(l.slope)
|
||||
c = l.coefficients
|
||||
d = -c[-1]/c[1] # y-intercept
|
||||
# apply the transform to a single point
|
||||
xf = Point(x, y)
|
||||
xf = xf.translate(y=-d).rotate(-a, o).scale(y=-1
|
||||
).rotate(a, o).translate(y=d)
|
||||
# replace every point using that transform
|
||||
reps = [(p, xf.xreplace({x: p.x, y: p.y})) for p in g.atoms(Point)]
|
||||
return g.xreplace(dict(reps))
|
||||
|
||||
def rotate(self, angle, pt=None):
|
||||
"""Rotate ``angle`` radians counterclockwise about Point ``pt``.
|
||||
|
||||
The default pt is the origin, Point(0, 0)
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
scale, translate
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Point, RegularPolygon, Polygon, pi
|
||||
>>> t = Polygon(*RegularPolygon(Point(0, 0), 1, 3).vertices)
|
||||
>>> t # vertex on x axis
|
||||
Triangle(Point2D(1, 0), Point2D(-1/2, sqrt(3)/2), Point2D(-1/2, -sqrt(3)/2))
|
||||
>>> t.rotate(pi/2) # vertex on y axis now
|
||||
Triangle(Point2D(0, 1), Point2D(-sqrt(3)/2, -1/2), Point2D(sqrt(3)/2, -1/2))
|
||||
|
||||
"""
|
||||
newargs = []
|
||||
for a in self.args:
|
||||
if isinstance(a, GeometryEntity):
|
||||
newargs.append(a.rotate(angle, pt))
|
||||
else:
|
||||
newargs.append(a)
|
||||
return type(self)(*newargs)
|
||||
|
||||
def scale(self, x=1, y=1, pt=None):
|
||||
"""Scale the object by multiplying the x,y-coordinates by x and y.
|
||||
|
||||
If pt is given, the scaling is done relative to that point; the
|
||||
object is shifted by -pt, scaled, and shifted by pt.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
rotate, translate
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import RegularPolygon, Point, Polygon
|
||||
>>> t = Polygon(*RegularPolygon(Point(0, 0), 1, 3).vertices)
|
||||
>>> t
|
||||
Triangle(Point2D(1, 0), Point2D(-1/2, sqrt(3)/2), Point2D(-1/2, -sqrt(3)/2))
|
||||
>>> t.scale(2)
|
||||
Triangle(Point2D(2, 0), Point2D(-1, sqrt(3)/2), Point2D(-1, -sqrt(3)/2))
|
||||
>>> t.scale(2, 2)
|
||||
Triangle(Point2D(2, 0), Point2D(-1, sqrt(3)), Point2D(-1, -sqrt(3)))
|
||||
|
||||
"""
|
||||
from sympy.geometry.point import Point
|
||||
if pt:
|
||||
pt = Point(pt, dim=2)
|
||||
return self.translate(*(-pt).args).scale(x, y).translate(*pt.args)
|
||||
return type(self)(*[a.scale(x, y) for a in self.args]) # if this fails, override this class
|
||||
|
||||
def translate(self, x=0, y=0):
|
||||
"""Shift the object by adding to the x,y-coordinates the values x and y.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
rotate, scale
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import RegularPolygon, Point, Polygon
|
||||
>>> t = Polygon(*RegularPolygon(Point(0, 0), 1, 3).vertices)
|
||||
>>> t
|
||||
Triangle(Point2D(1, 0), Point2D(-1/2, sqrt(3)/2), Point2D(-1/2, -sqrt(3)/2))
|
||||
>>> t.translate(2)
|
||||
Triangle(Point2D(3, 0), Point2D(3/2, sqrt(3)/2), Point2D(3/2, -sqrt(3)/2))
|
||||
>>> t.translate(2, 2)
|
||||
Triangle(Point2D(3, 2), Point2D(3/2, sqrt(3)/2 + 2), Point2D(3/2, 2 - sqrt(3)/2))
|
||||
|
||||
"""
|
||||
newargs = []
|
||||
for a in self.args:
|
||||
if isinstance(a, GeometryEntity):
|
||||
newargs.append(a.translate(x, y))
|
||||
else:
|
||||
newargs.append(a)
|
||||
return self.func(*newargs)
|
||||
|
||||
def parameter_value(self, other, t):
|
||||
"""Return the parameter corresponding to the given point.
|
||||
Evaluating an arbitrary point of the entity at this parameter
|
||||
value will return the given point.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Line, Point
|
||||
>>> from sympy.abc import t
|
||||
>>> a = Point(0, 0)
|
||||
>>> b = Point(2, 2)
|
||||
>>> Line(a, b).parameter_value((1, 1), t)
|
||||
{t: 1/2}
|
||||
>>> Line(a, b).arbitrary_point(t).subs(_)
|
||||
Point2D(1, 1)
|
||||
"""
|
||||
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")
|
||||
sol = solve(self.arbitrary_point(T) - other, T, dict=True)
|
||||
if not sol:
|
||||
raise ValueError("Given point is not on %s" % func_name(self))
|
||||
return {t: sol[0][T]}
|
||||
|
||||
|
||||
class GeometrySet(GeometryEntity, Set):
|
||||
"""Parent class of all GeometryEntity that are also Sets
|
||||
(compatible with sympy.sets)
|
||||
"""
|
||||
__slots__ = ()
|
||||
|
||||
def _contains(self, other):
|
||||
"""sympy.sets uses the _contains method, so include it for compatibility."""
|
||||
|
||||
if isinstance(other, Set) and other.is_FiniteSet:
|
||||
return all(self.__contains__(i) for i in other)
|
||||
|
||||
return self.__contains__(other)
|
||||
|
||||
@dispatch(GeometrySet, Set) # type:ignore # noqa:F811
|
||||
def union_sets(self, o): # noqa:F811
|
||||
""" Returns the union of self and o
|
||||
for use with sympy.sets.Set, if possible. """
|
||||
|
||||
|
||||
# if its a FiniteSet, merge any points
|
||||
# we contain and return a union with the rest
|
||||
if o.is_FiniteSet:
|
||||
other_points = [p for p in o if not self._contains(p)]
|
||||
if len(other_points) == len(o):
|
||||
return None
|
||||
return Union(self, FiniteSet(*other_points))
|
||||
if self._contains(o):
|
||||
return self
|
||||
return None
|
||||
|
||||
|
||||
@dispatch(GeometrySet, Set) # type: ignore # noqa:F811
|
||||
def intersection_sets(self, o): # noqa:F811
|
||||
""" Returns a sympy.sets.Set of intersection objects,
|
||||
if possible. """
|
||||
|
||||
from sympy.geometry.point import Point
|
||||
|
||||
try:
|
||||
# if o is a FiniteSet, find the intersection directly
|
||||
# to avoid infinite recursion
|
||||
if o.is_FiniteSet:
|
||||
inter = FiniteSet(*(p for p in o if self.contains(p)))
|
||||
else:
|
||||
inter = self.intersection(o)
|
||||
except NotImplementedError:
|
||||
# sympy.sets.Set.reduce expects None if an object
|
||||
# doesn't know how to simplify
|
||||
return None
|
||||
|
||||
# put the points in a FiniteSet
|
||||
points = FiniteSet(*[p for p in inter if isinstance(p, Point)])
|
||||
non_points = [p for p in inter if not isinstance(p, Point)]
|
||||
|
||||
return Union(*(non_points + [points]))
|
||||
|
||||
def translate(x, y):
|
||||
"""Return the matrix to translate a 2-D point by x and y."""
|
||||
rv = eye(3)
|
||||
rv[2, 0] = x
|
||||
rv[2, 1] = y
|
||||
return rv
|
||||
|
||||
|
||||
def scale(x, y, pt=None):
|
||||
"""Return the matrix to multiply a 2-D point's coordinates by x and y.
|
||||
|
||||
If pt is given, the scaling is done relative to that point."""
|
||||
rv = eye(3)
|
||||
rv[0, 0] = x
|
||||
rv[1, 1] = y
|
||||
if pt:
|
||||
from sympy.geometry.point import Point
|
||||
pt = Point(pt, dim=2)
|
||||
tr1 = translate(*(-pt).args)
|
||||
tr2 = translate(*pt.args)
|
||||
return tr1*rv*tr2
|
||||
return rv
|
||||
|
||||
|
||||
def rotate(th):
|
||||
"""Return the matrix to rotate a 2-D point about the origin by ``angle``.
|
||||
|
||||
The angle is measured in radians. To Point a point about a point other
|
||||
then the origin, translate the Point, do the rotation, and
|
||||
translate it back:
|
||||
|
||||
>>> from sympy.geometry.entity import rotate, translate
|
||||
>>> from sympy import Point, pi
|
||||
>>> rot_about_11 = translate(-1, -1)*rotate(pi/2)*translate(1, 1)
|
||||
>>> Point(1, 1).transform(rot_about_11)
|
||||
Point2D(1, 1)
|
||||
>>> Point(0, 0).transform(rot_about_11)
|
||||
Point2D(2, 0)
|
||||
"""
|
||||
s = sin(th)
|
||||
rv = eye(3)*cos(th)
|
||||
rv[0, 1] = s
|
||||
rv[1, 0] = -s
|
||||
rv[2, 2] = 1
|
||||
return rv
|
||||
@@ -0,0 +1,5 @@
|
||||
"""Geometry Errors."""
|
||||
|
||||
class GeometryError(ValueError):
|
||||
"""An exception raised by classes in the geometry module."""
|
||||
pass
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,422 @@
|
||||
"""Parabolic geometrical entity.
|
||||
|
||||
Contains
|
||||
* Parabola
|
||||
|
||||
"""
|
||||
|
||||
from sympy.core import S
|
||||
from sympy.core.sorting import ordered
|
||||
from sympy.core.symbol import _symbol, symbols
|
||||
from sympy.geometry.entity import GeometryEntity, GeometrySet
|
||||
from sympy.geometry.point import Point, Point2D
|
||||
from sympy.geometry.line import Line, Line2D, Ray2D, Segment2D, LinearEntity3D
|
||||
from sympy.geometry.ellipse import Ellipse
|
||||
from sympy.functions import sign
|
||||
from sympy.simplify.simplify import simplify
|
||||
from sympy.solvers.solvers import solve
|
||||
|
||||
|
||||
class Parabola(GeometrySet):
|
||||
"""A parabolic GeometryEntity.
|
||||
|
||||
A parabola is declared with a point, that is called 'focus', and
|
||||
a line, that is called 'directrix'.
|
||||
Only vertical or horizontal parabolas are currently supported.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
focus : Point
|
||||
Default value is Point(0, 0)
|
||||
directrix : Line
|
||||
|
||||
Attributes
|
||||
==========
|
||||
|
||||
focus
|
||||
directrix
|
||||
axis of symmetry
|
||||
focal length
|
||||
p parameter
|
||||
vertex
|
||||
eccentricity
|
||||
|
||||
Raises
|
||||
======
|
||||
ValueError
|
||||
When `focus` is not a two dimensional point.
|
||||
When `focus` is a point of directrix.
|
||||
NotImplementedError
|
||||
When `directrix` is neither horizontal nor vertical.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Parabola, Point, Line
|
||||
>>> p1 = Parabola(Point(0, 0), Line(Point(5, 8), Point(7,8)))
|
||||
>>> p1.focus
|
||||
Point2D(0, 0)
|
||||
>>> p1.directrix
|
||||
Line2D(Point2D(5, 8), Point2D(7, 8))
|
||||
|
||||
"""
|
||||
|
||||
def __new__(cls, focus=None, directrix=None, **kwargs):
|
||||
|
||||
if focus:
|
||||
focus = Point(focus, dim=2)
|
||||
else:
|
||||
focus = Point(0, 0)
|
||||
|
||||
directrix = Line(directrix)
|
||||
|
||||
if directrix.contains(focus):
|
||||
raise ValueError('The focus must not be a point of directrix')
|
||||
|
||||
return GeometryEntity.__new__(cls, focus, directrix, **kwargs)
|
||||
|
||||
@property
|
||||
def ambient_dimension(self):
|
||||
"""Returns the ambient dimension of parabola.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
ambient_dimension : integer
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Parabola, Point, Line
|
||||
>>> f1 = Point(0, 0)
|
||||
>>> p1 = Parabola(f1, Line(Point(5, 8), Point(7, 8)))
|
||||
>>> p1.ambient_dimension
|
||||
2
|
||||
|
||||
"""
|
||||
return 2
|
||||
|
||||
@property
|
||||
def axis_of_symmetry(self):
|
||||
"""Return the axis of symmetry of the parabola: a line
|
||||
perpendicular to the directrix passing through the focus.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
axis_of_symmetry : Line
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.geometry.line.Line
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Parabola, Point, Line
|
||||
>>> p1 = Parabola(Point(0, 0), Line(Point(5, 8), Point(7, 8)))
|
||||
>>> p1.axis_of_symmetry
|
||||
Line2D(Point2D(0, 0), Point2D(0, 1))
|
||||
|
||||
"""
|
||||
return self.directrix.perpendicular_line(self.focus)
|
||||
|
||||
@property
|
||||
def directrix(self):
|
||||
"""The directrix of the parabola.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
directrix : Line
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.geometry.line.Line
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Parabola, Point, Line
|
||||
>>> l1 = Line(Point(5, 8), Point(7, 8))
|
||||
>>> p1 = Parabola(Point(0, 0), l1)
|
||||
>>> p1.directrix
|
||||
Line2D(Point2D(5, 8), Point2D(7, 8))
|
||||
|
||||
"""
|
||||
return self.args[1]
|
||||
|
||||
@property
|
||||
def eccentricity(self):
|
||||
"""The eccentricity of the parabola.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
eccentricity : number
|
||||
|
||||
A parabola may also be characterized as a conic section with an
|
||||
eccentricity of 1. As a consequence of this, all parabolas are
|
||||
similar, meaning that while they can be different sizes,
|
||||
they are all the same shape.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
https://en.wikipedia.org/wiki/Parabola
|
||||
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Parabola, Point, Line
|
||||
>>> p1 = Parabola(Point(0, 0), Line(Point(5, 8), Point(7, 8)))
|
||||
>>> p1.eccentricity
|
||||
1
|
||||
|
||||
Notes
|
||||
-----
|
||||
The eccentricity for every Parabola is 1 by definition.
|
||||
|
||||
"""
|
||||
return S.One
|
||||
|
||||
def equation(self, x='x', y='y'):
|
||||
"""The equation of the parabola.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
x : str, optional
|
||||
Label for the x-axis. Default value is 'x'.
|
||||
y : str, optional
|
||||
Label for the y-axis. Default value is 'y'.
|
||||
|
||||
Returns
|
||||
=======
|
||||
equation : SymPy expression
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Parabola, Point, Line
|
||||
>>> p1 = Parabola(Point(0, 0), Line(Point(5, 8), Point(7, 8)))
|
||||
>>> p1.equation()
|
||||
-x**2 - 16*y + 64
|
||||
>>> p1.equation('f')
|
||||
-f**2 - 16*y + 64
|
||||
>>> p1.equation(y='z')
|
||||
-x**2 - 16*z + 64
|
||||
|
||||
"""
|
||||
x = _symbol(x, real=True)
|
||||
y = _symbol(y, real=True)
|
||||
|
||||
m = self.directrix.slope
|
||||
if m is S.Infinity:
|
||||
t1 = 4 * (self.p_parameter) * (x - self.vertex.x)
|
||||
t2 = (y - self.vertex.y)**2
|
||||
elif m == 0:
|
||||
t1 = 4 * (self.p_parameter) * (y - self.vertex.y)
|
||||
t2 = (x - self.vertex.x)**2
|
||||
else:
|
||||
a, b = self.focus
|
||||
c, d = self.directrix.coefficients[:2]
|
||||
t1 = (x - a)**2 + (y - b)**2
|
||||
t2 = self.directrix.equation(x, y)**2/(c**2 + d**2)
|
||||
return t1 - t2
|
||||
|
||||
@property
|
||||
def focal_length(self):
|
||||
"""The focal length of the parabola.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
focal_lenght : number or symbolic expression
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
The distance between the vertex and the focus
|
||||
(or the vertex and directrix), measured along the axis
|
||||
of symmetry, is the "focal length".
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
https://en.wikipedia.org/wiki/Parabola
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Parabola, Point, Line
|
||||
>>> p1 = Parabola(Point(0, 0), Line(Point(5, 8), Point(7, 8)))
|
||||
>>> p1.focal_length
|
||||
4
|
||||
|
||||
"""
|
||||
distance = self.directrix.distance(self.focus)
|
||||
focal_length = distance/2
|
||||
|
||||
return focal_length
|
||||
|
||||
@property
|
||||
def focus(self):
|
||||
"""The focus of the parabola.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
focus : Point
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.geometry.point.Point
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Parabola, Point, Line
|
||||
>>> f1 = Point(0, 0)
|
||||
>>> p1 = Parabola(f1, Line(Point(5, 8), Point(7, 8)))
|
||||
>>> p1.focus
|
||||
Point2D(0, 0)
|
||||
|
||||
"""
|
||||
return self.args[0]
|
||||
|
||||
def intersection(self, o):
|
||||
"""The intersection of the parabola and another geometrical entity `o`.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
o : GeometryEntity, LinearEntity
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
intersection : list of GeometryEntity objects
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Parabola, Point, Ellipse, Line, Segment
|
||||
>>> p1 = Point(0,0)
|
||||
>>> l1 = Line(Point(1, -2), Point(-1,-2))
|
||||
>>> parabola1 = Parabola(p1, l1)
|
||||
>>> parabola1.intersection(Ellipse(Point(0, 0), 2, 5))
|
||||
[Point2D(-2, 0), Point2D(2, 0)]
|
||||
>>> parabola1.intersection(Line(Point(-7, 3), Point(12, 3)))
|
||||
[Point2D(-4, 3), Point2D(4, 3)]
|
||||
>>> parabola1.intersection(Segment((-12, -65), (14, -68)))
|
||||
[]
|
||||
|
||||
"""
|
||||
x, y = symbols('x y', real=True)
|
||||
parabola_eq = self.equation()
|
||||
if isinstance(o, Parabola):
|
||||
if o in self:
|
||||
return [o]
|
||||
else:
|
||||
return list(ordered([Point(i) for i in solve(
|
||||
[parabola_eq, o.equation()], [x, y], set=True)[1]]))
|
||||
elif isinstance(o, Point2D):
|
||||
if simplify(parabola_eq.subs([(x, o._args[0]), (y, o._args[1])])) == 0:
|
||||
return [o]
|
||||
else:
|
||||
return []
|
||||
elif isinstance(o, (Segment2D, Ray2D)):
|
||||
result = solve([parabola_eq,
|
||||
Line2D(o.points[0], o.points[1]).equation()],
|
||||
[x, y], set=True)[1]
|
||||
return list(ordered([Point2D(i) for i in result if i in o]))
|
||||
elif isinstance(o, (Line2D, Ellipse)):
|
||||
return list(ordered([Point2D(i) for i in solve(
|
||||
[parabola_eq, o.equation()], [x, y], set=True)[1]]))
|
||||
elif isinstance(o, LinearEntity3D):
|
||||
raise TypeError('Entity must be two dimensional, not three dimensional')
|
||||
else:
|
||||
raise TypeError('Wrong type of argument were put')
|
||||
|
||||
@property
|
||||
def p_parameter(self):
|
||||
"""P is a parameter of parabola.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
p : number or symbolic expression
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
The absolute value of p is the focal length. The sign on p tells
|
||||
which way the parabola faces. Vertical parabolas that open up
|
||||
and horizontal that open right, give a positive value for p.
|
||||
Vertical parabolas that open down and horizontal that open left,
|
||||
give a negative value for p.
|
||||
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
https://www.sparknotes.com/math/precalc/conicsections/section2/
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Parabola, Point, Line
|
||||
>>> p1 = Parabola(Point(0, 0), Line(Point(5, 8), Point(7, 8)))
|
||||
>>> p1.p_parameter
|
||||
-4
|
||||
|
||||
"""
|
||||
m = self.directrix.slope
|
||||
if m is S.Infinity:
|
||||
x = self.directrix.coefficients[2]
|
||||
p = sign(self.focus.args[0] + x)
|
||||
elif m == 0:
|
||||
y = self.directrix.coefficients[2]
|
||||
p = sign(self.focus.args[1] + y)
|
||||
else:
|
||||
d = self.directrix.projection(self.focus)
|
||||
p = sign(self.focus.x - d.x)
|
||||
return p * self.focal_length
|
||||
|
||||
@property
|
||||
def vertex(self):
|
||||
"""The vertex of the parabola.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
vertex : Point
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.geometry.point.Point
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Parabola, Point, Line
|
||||
>>> p1 = Parabola(Point(0, 0), Line(Point(5, 8), Point(7, 8)))
|
||||
>>> p1.vertex
|
||||
Point2D(0, 4)
|
||||
|
||||
"""
|
||||
focus = self.focus
|
||||
m = self.directrix.slope
|
||||
if m is S.Infinity:
|
||||
vertex = Point(focus.args[0] - self.p_parameter, focus.args[1])
|
||||
elif m == 0:
|
||||
vertex = Point(focus.args[0], focus.args[1] - self.p_parameter)
|
||||
else:
|
||||
vertex = self.axis_of_symmetry.intersection(self)[0]
|
||||
return vertex
|
||||
@@ -0,0 +1,878 @@
|
||||
"""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
|
||||
|
||||
|
||||
@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:
|
||||
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
|
||||
return sum(i*j for i, j in zip(a, b)) == 0
|
||||
if isinstance(l, Plane):
|
||||
a = Matrix(l.normal_vector)
|
||||
b = Matrix(self.normal_vector)
|
||||
return bool(a.cross(b).is_zero_matrix)
|
||||
|
||||
|
||||
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
|
||||
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}
|
||||
|
||||
@property
|
||||
def ambient_dimension(self):
|
||||
return self.p1.ambient_dimension
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,120 @@
|
||||
from sympy.core.containers import Tuple
|
||||
from sympy.core.numbers import (Rational, pi)
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import (Symbol, symbols)
|
||||
from sympy.functions.elementary.hyperbolic import asinh
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.geometry import Curve, Line, Point, Ellipse, Ray, Segment, Circle, Polygon, RegularPolygon
|
||||
from sympy.testing.pytest import raises, slow
|
||||
|
||||
|
||||
def test_curve():
|
||||
x = Symbol('x', real=True)
|
||||
s = Symbol('s')
|
||||
z = Symbol('z')
|
||||
|
||||
# this curve is independent of the indicated parameter
|
||||
c = Curve([2*s, s**2], (z, 0, 2))
|
||||
|
||||
assert c.parameter == z
|
||||
assert c.functions == (2*s, s**2)
|
||||
assert c.arbitrary_point() == Point(2*s, s**2)
|
||||
assert c.arbitrary_point(z) == Point(2*s, s**2)
|
||||
|
||||
# this is how it is normally used
|
||||
c = Curve([2*s, s**2], (s, 0, 2))
|
||||
|
||||
assert c.parameter == s
|
||||
assert c.functions == (2*s, s**2)
|
||||
t = Symbol('t')
|
||||
# the t returned as assumptions
|
||||
assert c.arbitrary_point() != Point(2*t, t**2)
|
||||
t = Symbol('t', real=True)
|
||||
# now t has the same assumptions so the test passes
|
||||
assert c.arbitrary_point() == Point(2*t, t**2)
|
||||
assert c.arbitrary_point(z) == Point(2*z, z**2)
|
||||
assert c.arbitrary_point(c.parameter) == Point(2*s, s**2)
|
||||
assert c.arbitrary_point(None) == Point(2*s, s**2)
|
||||
assert c.plot_interval() == [t, 0, 2]
|
||||
assert c.plot_interval(z) == [z, 0, 2]
|
||||
|
||||
assert Curve([x, x], (x, 0, 1)).rotate(pi/2) == Curve([-x, x], (x, 0, 1))
|
||||
assert Curve([x, x], (x, 0, 1)).rotate(pi/2, (1, 2)).scale(2, 3).translate(
|
||||
1, 3).arbitrary_point(s) == \
|
||||
Line((0, 0), (1, 1)).rotate(pi/2, (1, 2)).scale(2, 3).translate(
|
||||
1, 3).arbitrary_point(s) == \
|
||||
Point(-2*s + 7, 3*s + 6)
|
||||
|
||||
raises(ValueError, lambda: Curve((s), (s, 1, 2)))
|
||||
raises(ValueError, lambda: Curve((x, x * 2), (1, x)))
|
||||
|
||||
raises(ValueError, lambda: Curve((s, s + t), (s, 1, 2)).arbitrary_point())
|
||||
raises(ValueError, lambda: Curve((s, s + t), (t, 1, 2)).arbitrary_point(s))
|
||||
|
||||
|
||||
@slow
|
||||
def test_free_symbols():
|
||||
a, b, c, d, e, f, s = symbols('a:f,s')
|
||||
assert Point(a, b).free_symbols == {a, b}
|
||||
assert Line((a, b), (c, d)).free_symbols == {a, b, c, d}
|
||||
assert Ray((a, b), (c, d)).free_symbols == {a, b, c, d}
|
||||
assert Ray((a, b), angle=c).free_symbols == {a, b, c}
|
||||
assert Segment((a, b), (c, d)).free_symbols == {a, b, c, d}
|
||||
assert Line((a, b), slope=c).free_symbols == {a, b, c}
|
||||
assert Curve((a*s, b*s), (s, c, d)).free_symbols == {a, b, c, d}
|
||||
assert Ellipse((a, b), c, d).free_symbols == {a, b, c, d}
|
||||
assert Ellipse((a, b), c, eccentricity=d).free_symbols == \
|
||||
{a, b, c, d}
|
||||
assert Ellipse((a, b), vradius=c, eccentricity=d).free_symbols == \
|
||||
{a, b, c, d}
|
||||
assert Circle((a, b), c).free_symbols == {a, b, c}
|
||||
assert Circle((a, b), (c, d), (e, f)).free_symbols == \
|
||||
{e, d, c, b, f, a}
|
||||
assert Polygon((a, b), (c, d), (e, f)).free_symbols == \
|
||||
{e, b, d, f, a, c}
|
||||
assert RegularPolygon((a, b), c, d, e).free_symbols == {e, a, b, c, d}
|
||||
|
||||
|
||||
def test_transform():
|
||||
x = Symbol('x', real=True)
|
||||
y = Symbol('y', real=True)
|
||||
c = Curve((x, x**2), (x, 0, 1))
|
||||
cout = Curve((2*x - 4, 3*x**2 - 10), (x, 0, 1))
|
||||
pts = [Point(0, 0), Point(S.Half, Rational(1, 4)), Point(1, 1)]
|
||||
pts_out = [Point(-4, -10), Point(-3, Rational(-37, 4)), Point(-2, -7)]
|
||||
|
||||
assert c.scale(2, 3, (4, 5)) == cout
|
||||
assert [c.subs(x, xi/2) for xi in Tuple(0, 1, 2)] == pts
|
||||
assert [cout.subs(x, xi/2) for xi in Tuple(0, 1, 2)] == pts_out
|
||||
assert Curve((x + y, 3*x), (x, 0, 1)).subs(y, S.Half) == \
|
||||
Curve((x + S.Half, 3*x), (x, 0, 1))
|
||||
assert Curve((x, 3*x), (x, 0, 1)).translate(4, 5) == \
|
||||
Curve((x + 4, 3*x + 5), (x, 0, 1))
|
||||
|
||||
|
||||
def test_length():
|
||||
t = Symbol('t', real=True)
|
||||
|
||||
c1 = Curve((t, 0), (t, 0, 1))
|
||||
assert c1.length == 1
|
||||
|
||||
c2 = Curve((t, t), (t, 0, 1))
|
||||
assert c2.length == sqrt(2)
|
||||
|
||||
c3 = Curve((t ** 2, t), (t, 2, 5))
|
||||
assert c3.length == -sqrt(17) - asinh(4) / 4 + asinh(10) / 4 + 5 * sqrt(101) / 2
|
||||
|
||||
|
||||
def test_parameter_value():
|
||||
t = Symbol('t')
|
||||
C = Curve([2*t, t**2], (t, 0, 2))
|
||||
assert C.parameter_value((2, 1), t) == {t: 1}
|
||||
raises(ValueError, lambda: C.parameter_value((2, 0), t))
|
||||
|
||||
|
||||
def test_issue_17997():
|
||||
t, s = symbols('t s')
|
||||
c = Curve((t, t**2), (t, 0, 10))
|
||||
p = Curve([2*s, s**2], (s, 0, 2))
|
||||
assert c(2) == Point(2, 4)
|
||||
assert p(1) == Point(2, 1)
|
||||
@@ -0,0 +1,613 @@
|
||||
from sympy.core import expand
|
||||
from sympy.core.numbers import (Rational, oo, pi)
|
||||
from sympy.core.relational import Eq
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import (Symbol, symbols)
|
||||
from sympy.functions.elementary.complexes import Abs
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.functions.elementary.trigonometric import sec
|
||||
from sympy.geometry.line import Segment2D
|
||||
from sympy.geometry.point import Point2D
|
||||
from sympy.geometry import (Circle, Ellipse, GeometryError, Line, Point,
|
||||
Polygon, Ray, RegularPolygon, Segment,
|
||||
Triangle, intersection)
|
||||
from sympy.testing.pytest import raises, slow
|
||||
from sympy.integrals.integrals import integrate
|
||||
from sympy.functions.special.elliptic_integrals import elliptic_e
|
||||
from sympy.functions.elementary.miscellaneous import Max
|
||||
|
||||
|
||||
def test_ellipse_equation_using_slope():
|
||||
from sympy.abc import x, y
|
||||
|
||||
e1 = Ellipse(Point(1, 0), 3, 2)
|
||||
assert str(e1.equation(_slope=1)) == str((-x + y + 1)**2/8 + (x + y - 1)**2/18 - 1)
|
||||
|
||||
e2 = Ellipse(Point(0, 0), 4, 1)
|
||||
assert str(e2.equation(_slope=1)) == str((-x + y)**2/2 + (x + y)**2/32 - 1)
|
||||
|
||||
e3 = Ellipse(Point(1, 5), 6, 2)
|
||||
assert str(e3.equation(_slope=2)) == str((-2*x + y - 3)**2/20 + (x + 2*y - 11)**2/180 - 1)
|
||||
|
||||
|
||||
def test_object_from_equation():
|
||||
from sympy.abc import x, y, a, b, c, d, e
|
||||
assert Circle(x**2 + y**2 + 3*x + 4*y - 8) == Circle(Point2D(S(-3) / 2, -2), sqrt(57) / 2)
|
||||
assert Circle(x**2 + y**2 + 6*x + 8*y + 25) == Circle(Point2D(-3, -4), 0)
|
||||
assert Circle(a**2 + b**2 + 6*a + 8*b + 25, x='a', y='b') == Circle(Point2D(-3, -4), 0)
|
||||
assert Circle(x**2 + y**2 - 25) == Circle(Point2D(0, 0), 5)
|
||||
assert Circle(x**2 + y**2) == Circle(Point2D(0, 0), 0)
|
||||
assert Circle(a**2 + b**2, x='a', y='b') == Circle(Point2D(0, 0), 0)
|
||||
assert Circle(x**2 + y**2 + 6*x + 8) == Circle(Point2D(-3, 0), 1)
|
||||
assert Circle(x**2 + y**2 + 6*y + 8) == Circle(Point2D(0, -3), 1)
|
||||
assert Circle((x - 1)**2 + y**2 - 9) == Circle(Point2D(1, 0), 3)
|
||||
assert Circle(6*(x**2) + 6*(y**2) + 6*x + 8*y - 25) == Circle(Point2D(Rational(-1, 2), Rational(-2, 3)), 5*sqrt(7)/6)
|
||||
assert Circle(Eq(a**2 + b**2, 25), x='a', y=b) == Circle(Point2D(0, 0), 5)
|
||||
raises(GeometryError, lambda: Circle(x**2 + y**2 + 3*x + 4*y + 26))
|
||||
raises(GeometryError, lambda: Circle(x**2 + y**2 + 25))
|
||||
raises(GeometryError, lambda: Circle(a**2 + b**2 + 25, x='a', y='b'))
|
||||
raises(GeometryError, lambda: Circle(x**2 + 6*y + 8))
|
||||
raises(GeometryError, lambda: Circle(6*(x ** 2) + 4*(y**2) + 6*x + 8*y + 25))
|
||||
raises(ValueError, lambda: Circle(a**2 + b**2 + 3*a + 4*b - 8))
|
||||
# .equation() adds 'real=True' assumption; '==' would fail if assumptions differed
|
||||
x, y = symbols('x y', real=True)
|
||||
eq = a*x**2 + a*y**2 + c*x + d*y + e
|
||||
assert expand(Circle(eq).equation()*a) == eq
|
||||
|
||||
|
||||
@slow
|
||||
def test_ellipse_geom():
|
||||
x = Symbol('x', real=True)
|
||||
y = Symbol('y', real=True)
|
||||
t = Symbol('t', real=True)
|
||||
y1 = Symbol('y1', real=True)
|
||||
half = S.Half
|
||||
p1 = Point(0, 0)
|
||||
p2 = Point(1, 1)
|
||||
p4 = Point(0, 1)
|
||||
|
||||
e1 = Ellipse(p1, 1, 1)
|
||||
e2 = Ellipse(p2, half, 1)
|
||||
e3 = Ellipse(p1, y1, y1)
|
||||
c1 = Circle(p1, 1)
|
||||
c2 = Circle(p2, 1)
|
||||
c3 = Circle(Point(sqrt(2), sqrt(2)), 1)
|
||||
l1 = Line(p1, p2)
|
||||
|
||||
# Test creation with three points
|
||||
cen, rad = Point(3*half, 2), 5*half
|
||||
assert Circle(Point(0, 0), Point(3, 0), Point(0, 4)) == Circle(cen, rad)
|
||||
assert Circle(Point(0, 0), Point(1, 1), Point(2, 2)) == Segment2D(Point2D(0, 0), Point2D(2, 2))
|
||||
|
||||
raises(ValueError, lambda: Ellipse(None, None, None, 1))
|
||||
raises(ValueError, lambda: Ellipse())
|
||||
raises(GeometryError, lambda: Circle(Point(0, 0)))
|
||||
raises(GeometryError, lambda: Circle(Symbol('x')*Symbol('y')))
|
||||
|
||||
# Basic Stuff
|
||||
assert Ellipse(None, 1, 1).center == Point(0, 0)
|
||||
assert e1 == c1
|
||||
assert e1 != e2
|
||||
assert e1 != l1
|
||||
assert p4 in e1
|
||||
assert e1 in e1
|
||||
assert e2 in e2
|
||||
assert 1 not in e2
|
||||
assert p2 not in e2
|
||||
assert e1.area == pi
|
||||
assert e2.area == pi/2
|
||||
assert e3.area == pi*y1*abs(y1)
|
||||
assert c1.area == e1.area
|
||||
assert c1.circumference == e1.circumference
|
||||
assert e3.circumference == 2*pi*y1
|
||||
assert e1.plot_interval() == e2.plot_interval() == [t, -pi, pi]
|
||||
assert e1.plot_interval(x) == e2.plot_interval(x) == [x, -pi, pi]
|
||||
|
||||
assert c1.minor == 1
|
||||
assert c1.major == 1
|
||||
assert c1.hradius == 1
|
||||
assert c1.vradius == 1
|
||||
|
||||
assert Ellipse((1, 1), 0, 0) == Point(1, 1)
|
||||
assert Ellipse((1, 1), 1, 0) == Segment(Point(0, 1), Point(2, 1))
|
||||
assert Ellipse((1, 1), 0, 1) == Segment(Point(1, 0), Point(1, 2))
|
||||
|
||||
# Private Functions
|
||||
assert hash(c1) == hash(Circle(Point(1, 0), Point(0, 1), Point(0, -1)))
|
||||
assert c1 in e1
|
||||
assert (Line(p1, p2) in e1) is False
|
||||
assert e1.__cmp__(e1) == 0
|
||||
assert e1.__cmp__(Point(0, 0)) > 0
|
||||
|
||||
# Encloses
|
||||
assert e1.encloses(Segment(Point(-0.5, -0.5), Point(0.5, 0.5))) is True
|
||||
assert e1.encloses(Line(p1, p2)) is False
|
||||
assert e1.encloses(Ray(p1, p2)) is False
|
||||
assert e1.encloses(e1) is False
|
||||
assert e1.encloses(
|
||||
Polygon(Point(-0.5, -0.5), Point(-0.5, 0.5), Point(0.5, 0.5))) is True
|
||||
assert e1.encloses(RegularPolygon(p1, 0.5, 3)) is True
|
||||
assert e1.encloses(RegularPolygon(p1, 5, 3)) is False
|
||||
assert e1.encloses(RegularPolygon(p2, 5, 3)) is False
|
||||
|
||||
assert e2.arbitrary_point() in e2
|
||||
raises(ValueError, lambda: Ellipse(Point(x, y), 1, 1).arbitrary_point(parameter='x'))
|
||||
|
||||
# Foci
|
||||
f1, f2 = Point(sqrt(12), 0), Point(-sqrt(12), 0)
|
||||
ef = Ellipse(Point(0, 0), 4, 2)
|
||||
assert ef.foci in [(f1, f2), (f2, f1)]
|
||||
|
||||
# Tangents
|
||||
v = sqrt(2) / 2
|
||||
p1_1 = Point(v, v)
|
||||
p1_2 = p2 + Point(half, 0)
|
||||
p1_3 = p2 + Point(0, 1)
|
||||
assert e1.tangent_lines(p4) == c1.tangent_lines(p4)
|
||||
assert e2.tangent_lines(p1_2) == [Line(Point(Rational(3, 2), 1), Point(Rational(3, 2), S.Half))]
|
||||
assert e2.tangent_lines(p1_3) == [Line(Point(1, 2), Point(Rational(5, 4), 2))]
|
||||
assert c1.tangent_lines(p1_1) != [Line(p1_1, Point(0, sqrt(2)))]
|
||||
assert c1.tangent_lines(p1) == []
|
||||
assert e2.is_tangent(Line(p1_2, p2 + Point(half, 1)))
|
||||
assert e2.is_tangent(Line(p1_3, p2 + Point(half, 1)))
|
||||
assert c1.is_tangent(Line(p1_1, Point(0, sqrt(2))))
|
||||
assert e1.is_tangent(Line(Point(0, 0), Point(1, 1))) is False
|
||||
assert c1.is_tangent(e1) is True
|
||||
assert c1.is_tangent(Ellipse(Point(2, 0), 1, 1)) is True
|
||||
assert c1.is_tangent(
|
||||
Polygon(Point(1, 1), Point(1, -1), Point(2, 0))) is False
|
||||
assert c1.is_tangent(
|
||||
Polygon(Point(1, 1), Point(1, 0), Point(2, 0))) is False
|
||||
assert Circle(Point(5, 5), 3).is_tangent(Circle(Point(0, 5), 1)) is False
|
||||
|
||||
assert Ellipse(Point(5, 5), 2, 1).tangent_lines(Point(0, 0)) == \
|
||||
[Line(Point(0, 0), Point(Rational(77, 25), Rational(132, 25))),
|
||||
Line(Point(0, 0), Point(Rational(33, 5), Rational(22, 5)))]
|
||||
assert Ellipse(Point(5, 5), 2, 1).tangent_lines(Point(3, 4)) == \
|
||||
[Line(Point(3, 4), Point(4, 4)), Line(Point(3, 4), Point(3, 5))]
|
||||
assert Circle(Point(5, 5), 2).tangent_lines(Point(3, 3)) == \
|
||||
[Line(Point(3, 3), Point(4, 3)), Line(Point(3, 3), Point(3, 4))]
|
||||
assert Circle(Point(5, 5), 2).tangent_lines(Point(5 - 2*sqrt(2), 5)) == \
|
||||
[Line(Point(5 - 2*sqrt(2), 5), Point(5 - sqrt(2), 5 - sqrt(2))),
|
||||
Line(Point(5 - 2*sqrt(2), 5), Point(5 - sqrt(2), 5 + sqrt(2))), ]
|
||||
assert Circle(Point(5, 5), 5).tangent_lines(Point(4, 0)) == \
|
||||
[Line(Point(4, 0), Point(Rational(40, 13), Rational(5, 13))),
|
||||
Line(Point(4, 0), Point(5, 0))]
|
||||
assert Circle(Point(5, 5), 5).tangent_lines(Point(0, 6)) == \
|
||||
[Line(Point(0, 6), Point(0, 7)),
|
||||
Line(Point(0, 6), Point(Rational(5, 13), Rational(90, 13)))]
|
||||
|
||||
# for numerical calculations, we shouldn't demand exact equality,
|
||||
# so only test up to the desired precision
|
||||
def lines_close(l1, l2, prec):
|
||||
""" tests whether l1 and 12 are within 10**(-prec)
|
||||
of each other """
|
||||
return abs(l1.p1 - l2.p1) < 10**(-prec) and abs(l1.p2 - l2.p2) < 10**(-prec)
|
||||
def line_list_close(ll1, ll2, prec):
|
||||
return all(lines_close(l1, l2, prec) for l1, l2 in zip(ll1, ll2))
|
||||
|
||||
e = Ellipse(Point(0, 0), 2, 1)
|
||||
assert e.normal_lines(Point(0, 0)) == \
|
||||
[Line(Point(0, 0), Point(0, 1)), Line(Point(0, 0), Point(1, 0))]
|
||||
assert e.normal_lines(Point(1, 0)) == \
|
||||
[Line(Point(0, 0), Point(1, 0))]
|
||||
assert e.normal_lines((0, 1)) == \
|
||||
[Line(Point(0, 0), Point(0, 1))]
|
||||
assert line_list_close(e.normal_lines(Point(1, 1), 2), [
|
||||
Line(Point(Rational(-51, 26), Rational(-1, 5)), Point(Rational(-25, 26), Rational(17, 83))),
|
||||
Line(Point(Rational(28, 29), Rational(-7, 8)), Point(Rational(57, 29), Rational(-9, 2)))], 2)
|
||||
# test the failure of Poly.intervals and checks a point on the boundary
|
||||
p = Point(sqrt(3), S.Half)
|
||||
assert p in e
|
||||
assert line_list_close(e.normal_lines(p, 2), [
|
||||
Line(Point(Rational(-341, 171), Rational(-1, 13)), Point(Rational(-170, 171), Rational(5, 64))),
|
||||
Line(Point(Rational(26, 15), Rational(-1, 2)), Point(Rational(41, 15), Rational(-43, 26)))], 2)
|
||||
# be sure to use the slope that isn't undefined on boundary
|
||||
e = Ellipse((0, 0), 2, 2*sqrt(3)/3)
|
||||
assert line_list_close(e.normal_lines((1, 1), 2), [
|
||||
Line(Point(Rational(-64, 33), Rational(-20, 71)), Point(Rational(-31, 33), Rational(2, 13))),
|
||||
Line(Point(1, -1), Point(2, -4))], 2)
|
||||
# general ellipse fails except under certain conditions
|
||||
e = Ellipse((0, 0), x, 1)
|
||||
assert e.normal_lines((x + 1, 0)) == [Line(Point(0, 0), Point(1, 0))]
|
||||
raises(NotImplementedError, lambda: e.normal_lines((x + 1, 1)))
|
||||
# Properties
|
||||
major = 3
|
||||
minor = 1
|
||||
e4 = Ellipse(p2, minor, major)
|
||||
assert e4.focus_distance == sqrt(major**2 - minor**2)
|
||||
ecc = e4.focus_distance / major
|
||||
assert e4.eccentricity == ecc
|
||||
assert e4.periapsis == major*(1 - ecc)
|
||||
assert e4.apoapsis == major*(1 + ecc)
|
||||
assert e4.semilatus_rectum == major*(1 - ecc ** 2)
|
||||
# independent of orientation
|
||||
e4 = Ellipse(p2, major, minor)
|
||||
assert e4.focus_distance == sqrt(major**2 - minor**2)
|
||||
ecc = e4.focus_distance / major
|
||||
assert e4.eccentricity == ecc
|
||||
assert e4.periapsis == major*(1 - ecc)
|
||||
assert e4.apoapsis == major*(1 + ecc)
|
||||
|
||||
# Intersection
|
||||
l1 = Line(Point(1, -5), Point(1, 5))
|
||||
l2 = Line(Point(-5, -1), Point(5, -1))
|
||||
l3 = Line(Point(-1, -1), Point(1, 1))
|
||||
l4 = Line(Point(-10, 0), Point(0, 10))
|
||||
pts_c1_l3 = [Point(sqrt(2)/2, sqrt(2)/2), Point(-sqrt(2)/2, -sqrt(2)/2)]
|
||||
|
||||
assert intersection(e2, l4) == []
|
||||
assert intersection(c1, Point(1, 0)) == [Point(1, 0)]
|
||||
assert intersection(c1, l1) == [Point(1, 0)]
|
||||
assert intersection(c1, l2) == [Point(0, -1)]
|
||||
assert intersection(c1, l3) in [pts_c1_l3, [pts_c1_l3[1], pts_c1_l3[0]]]
|
||||
assert intersection(c1, c2) == [Point(0, 1), Point(1, 0)]
|
||||
assert intersection(c1, c3) == [Point(sqrt(2)/2, sqrt(2)/2)]
|
||||
assert e1.intersection(l1) == [Point(1, 0)]
|
||||
assert e2.intersection(l4) == []
|
||||
assert e1.intersection(Circle(Point(0, 2), 1)) == [Point(0, 1)]
|
||||
assert e1.intersection(Circle(Point(5, 0), 1)) == []
|
||||
assert e1.intersection(Ellipse(Point(2, 0), 1, 1)) == [Point(1, 0)]
|
||||
assert e1.intersection(Ellipse(Point(5, 0), 1, 1)) == []
|
||||
assert e1.intersection(Point(2, 0)) == []
|
||||
assert e1.intersection(e1) == e1
|
||||
assert intersection(Ellipse(Point(0, 0), 2, 1), Ellipse(Point(3, 0), 1, 2)) == [Point(2, 0)]
|
||||
assert intersection(Circle(Point(0, 0), 2), Circle(Point(3, 0), 1)) == [Point(2, 0)]
|
||||
assert intersection(Circle(Point(0, 0), 2), Circle(Point(7, 0), 1)) == []
|
||||
assert intersection(Ellipse(Point(0, 0), 5, 17), Ellipse(Point(4, 0), 1, 0.2)
|
||||
) == [Point(5.0, 0, evaluate=False)]
|
||||
assert intersection(Ellipse(Point(0, 0), 5, 17), Ellipse(Point(4, 0), 0.999, 0.2)) == []
|
||||
assert Circle((0, 0), S.Half).intersection(
|
||||
Triangle((-1, 0), (1, 0), (0, 1))) == [
|
||||
Point(Rational(-1, 2), 0), Point(S.Half, 0)]
|
||||
raises(TypeError, lambda: intersection(e2, Line((0, 0, 0), (0, 0, 1))))
|
||||
raises(TypeError, lambda: intersection(e2, Rational(12)))
|
||||
raises(TypeError, lambda: Ellipse.intersection(e2, 1))
|
||||
# some special case intersections
|
||||
csmall = Circle(p1, 3)
|
||||
cbig = Circle(p1, 5)
|
||||
cout = Circle(Point(5, 5), 1)
|
||||
# one circle inside of another
|
||||
assert csmall.intersection(cbig) == []
|
||||
# separate circles
|
||||
assert csmall.intersection(cout) == []
|
||||
# coincident circles
|
||||
assert csmall.intersection(csmall) == csmall
|
||||
|
||||
v = sqrt(2)
|
||||
t1 = Triangle(Point(0, v), Point(0, -v), Point(v, 0))
|
||||
points = intersection(t1, c1)
|
||||
assert len(points) == 4
|
||||
assert Point(0, 1) in points
|
||||
assert Point(0, -1) in points
|
||||
assert Point(v/2, v/2) in points
|
||||
assert Point(v/2, -v/2) in points
|
||||
|
||||
circ = Circle(Point(0, 0), 5)
|
||||
elip = Ellipse(Point(0, 0), 5, 20)
|
||||
assert intersection(circ, elip) in \
|
||||
[[Point(5, 0), Point(-5, 0)], [Point(-5, 0), Point(5, 0)]]
|
||||
assert elip.tangent_lines(Point(0, 0)) == []
|
||||
elip = Ellipse(Point(0, 0), 3, 2)
|
||||
assert elip.tangent_lines(Point(3, 0)) == \
|
||||
[Line(Point(3, 0), Point(3, -12))]
|
||||
|
||||
e1 = Ellipse(Point(0, 0), 5, 10)
|
||||
e2 = Ellipse(Point(2, 1), 4, 8)
|
||||
a = Rational(53, 17)
|
||||
c = 2*sqrt(3991)/17
|
||||
ans = [Point(a - c/8, a/2 + c), Point(a + c/8, a/2 - c)]
|
||||
assert e1.intersection(e2) == ans
|
||||
e2 = Ellipse(Point(x, y), 4, 8)
|
||||
c = sqrt(3991)
|
||||
ans = [Point(-c/68 + a, c*Rational(2, 17) + a/2), Point(c/68 + a, c*Rational(-2, 17) + a/2)]
|
||||
assert [p.subs({x: 2, y:1}) for p in e1.intersection(e2)] == ans
|
||||
|
||||
# Combinations of above
|
||||
assert e3.is_tangent(e3.tangent_lines(p1 + Point(y1, 0))[0])
|
||||
|
||||
e = Ellipse((1, 2), 3, 2)
|
||||
assert e.tangent_lines(Point(10, 0)) == \
|
||||
[Line(Point(10, 0), Point(1, 0)),
|
||||
Line(Point(10, 0), Point(Rational(14, 5), Rational(18, 5)))]
|
||||
|
||||
# encloses_point
|
||||
e = Ellipse((0, 0), 1, 2)
|
||||
assert e.encloses_point(e.center)
|
||||
assert e.encloses_point(e.center + Point(0, e.vradius - Rational(1, 10)))
|
||||
assert e.encloses_point(e.center + Point(e.hradius - Rational(1, 10), 0))
|
||||
assert e.encloses_point(e.center + Point(e.hradius, 0)) is False
|
||||
assert e.encloses_point(
|
||||
e.center + Point(e.hradius + Rational(1, 10), 0)) is False
|
||||
e = Ellipse((0, 0), 2, 1)
|
||||
assert e.encloses_point(e.center)
|
||||
assert e.encloses_point(e.center + Point(0, e.vradius - Rational(1, 10)))
|
||||
assert e.encloses_point(e.center + Point(e.hradius - Rational(1, 10), 0))
|
||||
assert e.encloses_point(e.center + Point(e.hradius, 0)) is False
|
||||
assert e.encloses_point(
|
||||
e.center + Point(e.hradius + Rational(1, 10), 0)) is False
|
||||
assert c1.encloses_point(Point(1, 0)) is False
|
||||
assert c1.encloses_point(Point(0.3, 0.4)) is True
|
||||
|
||||
assert e.scale(2, 3) == Ellipse((0, 0), 4, 3)
|
||||
assert e.scale(3, 6) == Ellipse((0, 0), 6, 6)
|
||||
assert e.rotate(pi) == e
|
||||
assert e.rotate(pi, (1, 2)) == Ellipse(Point(2, 4), 2, 1)
|
||||
raises(NotImplementedError, lambda: e.rotate(pi/3))
|
||||
|
||||
# Circle rotation tests (Issue #11743)
|
||||
# Link - https://github.com/sympy/sympy/issues/11743
|
||||
cir = Circle(Point(1, 0), 1)
|
||||
assert cir.rotate(pi/2) == Circle(Point(0, 1), 1)
|
||||
assert cir.rotate(pi/3) == Circle(Point(S.Half, sqrt(3)/2), 1)
|
||||
assert cir.rotate(pi/3, Point(1, 0)) == Circle(Point(1, 0), 1)
|
||||
assert cir.rotate(pi/3, Point(0, 1)) == Circle(Point(S.Half + sqrt(3)/2, S.Half + sqrt(3)/2), 1)
|
||||
|
||||
|
||||
def test_construction():
|
||||
e1 = Ellipse(hradius=2, vradius=1, eccentricity=None)
|
||||
assert e1.eccentricity == sqrt(3)/2
|
||||
|
||||
e2 = Ellipse(hradius=2, vradius=None, eccentricity=sqrt(3)/2)
|
||||
assert e2.vradius == 1
|
||||
|
||||
e3 = Ellipse(hradius=None, vradius=1, eccentricity=sqrt(3)/2)
|
||||
assert e3.hradius == 2
|
||||
|
||||
# filter(None, iterator) filters out anything falsey, including 0
|
||||
# eccentricity would be filtered out in this case and the constructor would throw an error
|
||||
e4 = Ellipse(Point(0, 0), hradius=1, eccentricity=0)
|
||||
assert e4.vradius == 1
|
||||
|
||||
#tests for eccentricity > 1
|
||||
raises(GeometryError, lambda: Ellipse(Point(3, 1), hradius=3, eccentricity = S(3)/2))
|
||||
raises(GeometryError, lambda: Ellipse(Point(3, 1), hradius=3, eccentricity=sec(5)))
|
||||
raises(GeometryError, lambda: Ellipse(Point(3, 1), hradius=3, eccentricity=S.Pi-S(2)))
|
||||
|
||||
#tests for eccentricity = 1
|
||||
#if vradius is not defined
|
||||
assert Ellipse(None, 1, None, 1).length == 2
|
||||
#if hradius is not defined
|
||||
raises(GeometryError, lambda: Ellipse(None, None, 1, eccentricity = 1))
|
||||
|
||||
#tests for eccentricity < 0
|
||||
raises(GeometryError, lambda: Ellipse(Point(3, 1), hradius=3, eccentricity = -3))
|
||||
raises(GeometryError, lambda: Ellipse(Point(3, 1), hradius=3, eccentricity = -0.5))
|
||||
|
||||
def test_ellipse_random_point():
|
||||
y1 = Symbol('y1', real=True)
|
||||
e3 = Ellipse(Point(0, 0), y1, y1)
|
||||
rx, ry = Symbol('rx'), Symbol('ry')
|
||||
for ind in range(0, 5):
|
||||
r = e3.random_point()
|
||||
# substitution should give zero*y1**2
|
||||
assert e3.equation(rx, ry).subs(zip((rx, ry), r.args)).equals(0)
|
||||
# test for the case with seed
|
||||
r = e3.random_point(seed=1)
|
||||
assert e3.equation(rx, ry).subs(zip((rx, ry), r.args)).equals(0)
|
||||
|
||||
|
||||
def test_repr():
|
||||
assert repr(Circle((0, 1), 2)) == 'Circle(Point2D(0, 1), 2)'
|
||||
|
||||
|
||||
def test_transform():
|
||||
c = Circle((1, 1), 2)
|
||||
assert c.scale(-1) == Circle((-1, 1), 2)
|
||||
assert c.scale(y=-1) == Circle((1, -1), 2)
|
||||
assert c.scale(2) == Ellipse((2, 1), 4, 2)
|
||||
|
||||
assert Ellipse((0, 0), 2, 3).scale(2, 3, (4, 5)) == \
|
||||
Ellipse(Point(-4, -10), 4, 9)
|
||||
assert Circle((0, 0), 2).scale(2, 3, (4, 5)) == \
|
||||
Ellipse(Point(-4, -10), 4, 6)
|
||||
assert Ellipse((0, 0), 2, 3).scale(3, 3, (4, 5)) == \
|
||||
Ellipse(Point(-8, -10), 6, 9)
|
||||
assert Circle((0, 0), 2).scale(3, 3, (4, 5)) == \
|
||||
Circle(Point(-8, -10), 6)
|
||||
assert Circle(Point(-8, -10), 6).scale(Rational(1, 3), Rational(1, 3), (4, 5)) == \
|
||||
Circle((0, 0), 2)
|
||||
assert Circle((0, 0), 2).translate(4, 5) == \
|
||||
Circle((4, 5), 2)
|
||||
assert Circle((0, 0), 2).scale(3, 3) == \
|
||||
Circle((0, 0), 6)
|
||||
|
||||
|
||||
def test_bounds():
|
||||
e1 = Ellipse(Point(0, 0), 3, 5)
|
||||
e2 = Ellipse(Point(2, -2), 7, 7)
|
||||
c1 = Circle(Point(2, -2), 7)
|
||||
c2 = Circle(Point(-2, 0), Point(0, 2), Point(2, 0))
|
||||
assert e1.bounds == (-3, -5, 3, 5)
|
||||
assert e2.bounds == (-5, -9, 9, 5)
|
||||
assert c1.bounds == (-5, -9, 9, 5)
|
||||
assert c2.bounds == (-2, -2, 2, 2)
|
||||
|
||||
|
||||
def test_reflect():
|
||||
b = Symbol('b')
|
||||
m = Symbol('m')
|
||||
l = Line((0, b), slope=m)
|
||||
t1 = Triangle((0, 0), (1, 0), (2, 3))
|
||||
assert t1.area == -t1.reflect(l).area
|
||||
e = Ellipse((1, 0), 1, 2)
|
||||
assert e.area == -e.reflect(Line((1, 0), slope=0)).area
|
||||
assert e.area == -e.reflect(Line((1, 0), slope=oo)).area
|
||||
raises(NotImplementedError, lambda: e.reflect(Line((1, 0), slope=m)))
|
||||
assert Circle((0, 1), 1).reflect(Line((0, 0), (1, 1))) == Circle(Point2D(1, 0), -1)
|
||||
|
||||
|
||||
def test_is_tangent():
|
||||
e1 = Ellipse(Point(0, 0), 3, 5)
|
||||
c1 = Circle(Point(2, -2), 7)
|
||||
assert e1.is_tangent(Point(0, 0)) is False
|
||||
assert e1.is_tangent(Point(3, 0)) is False
|
||||
assert e1.is_tangent(e1) is True
|
||||
assert e1.is_tangent(Ellipse((0, 0), 1, 2)) is False
|
||||
assert e1.is_tangent(Ellipse((0, 0), 3, 2)) is True
|
||||
assert c1.is_tangent(Ellipse((2, -2), 7, 1)) is True
|
||||
assert c1.is_tangent(Circle((11, -2), 2)) is True
|
||||
assert c1.is_tangent(Circle((7, -2), 2)) is True
|
||||
assert c1.is_tangent(Ray((-5, -2), (-15, -20))) is False
|
||||
assert c1.is_tangent(Ray((-3, -2), (-15, -20))) is False
|
||||
assert c1.is_tangent(Ray((-3, -22), (15, 20))) is False
|
||||
assert c1.is_tangent(Ray((9, 20), (9, -20))) is True
|
||||
assert c1.is_tangent(Ray((2, 5), (9, 5))) is True
|
||||
assert c1.is_tangent(Segment((2, 5), (9, 5))) is True
|
||||
assert e1.is_tangent(Segment((2, 2), (-7, 7))) is False
|
||||
assert e1.is_tangent(Segment((0, 0), (1, 2))) is False
|
||||
assert c1.is_tangent(Segment((0, 0), (-5, -2))) is False
|
||||
assert e1.is_tangent(Segment((3, 0), (12, 12))) is False
|
||||
assert e1.is_tangent(Segment((12, 12), (3, 0))) is False
|
||||
assert e1.is_tangent(Segment((-3, 0), (3, 0))) is False
|
||||
assert e1.is_tangent(Segment((-3, 5), (3, 5))) is True
|
||||
assert e1.is_tangent(Line((10, 0), (10, 10))) is False
|
||||
assert e1.is_tangent(Line((0, 0), (1, 1))) is False
|
||||
assert e1.is_tangent(Line((-3, 0), (-2.99, -0.001))) is False
|
||||
assert e1.is_tangent(Line((-3, 0), (-3, 1))) is True
|
||||
assert e1.is_tangent(Polygon((0, 0), (5, 5), (5, -5))) is False
|
||||
assert e1.is_tangent(Polygon((-100, -50), (-40, -334), (-70, -52))) is False
|
||||
assert e1.is_tangent(Polygon((-3, 0), (3, 0), (0, 1))) is False
|
||||
assert e1.is_tangent(Polygon((-3, 0), (3, 0), (0, 5))) is False
|
||||
assert e1.is_tangent(Polygon((-3, 0), (0, -5), (3, 0), (0, 5))) is False
|
||||
assert e1.is_tangent(Polygon((-3, -5), (-3, 5), (3, 5), (3, -5))) is True
|
||||
assert c1.is_tangent(Polygon((-3, -5), (-3, 5), (3, 5), (3, -5))) is False
|
||||
assert e1.is_tangent(Polygon((0, 0), (3, 0), (7, 7), (0, 5))) is False
|
||||
assert e1.is_tangent(Polygon((3, 12), (3, -12), (6, 5))) is False
|
||||
assert e1.is_tangent(Polygon((3, 12), (3, -12), (0, -5), (0, 5))) is False
|
||||
assert e1.is_tangent(Polygon((3, 0), (5, 7), (6, -5))) is False
|
||||
assert c1.is_tangent(Segment((0, 0), (-5, -2))) is False
|
||||
assert e1.is_tangent(Segment((-3, 0), (3, 0))) is False
|
||||
assert e1.is_tangent(Segment((-3, 5), (3, 5))) is True
|
||||
assert e1.is_tangent(Polygon((0, 0), (5, 5), (5, -5))) is False
|
||||
assert e1.is_tangent(Polygon((-100, -50), (-40, -334), (-70, -52))) is False
|
||||
assert e1.is_tangent(Polygon((-3, -5), (-3, 5), (3, 5), (3, -5))) is True
|
||||
assert c1.is_tangent(Polygon((-3, -5), (-3, 5), (3, 5), (3, -5))) is False
|
||||
assert e1.is_tangent(Polygon((3, 12), (3, -12), (0, -5), (0, 5))) is False
|
||||
assert e1.is_tangent(Polygon((3, 0), (5, 7), (6, -5))) is False
|
||||
raises(TypeError, lambda: e1.is_tangent(Point(0, 0, 0)))
|
||||
raises(TypeError, lambda: e1.is_tangent(Rational(5)))
|
||||
|
||||
|
||||
def test_parameter_value():
|
||||
t = Symbol('t')
|
||||
e = Ellipse(Point(0, 0), 3, 5)
|
||||
assert e.parameter_value((3, 0), t) == {t: 0}
|
||||
raises(ValueError, lambda: e.parameter_value((4, 0), t))
|
||||
|
||||
|
||||
@slow
|
||||
def test_second_moment_of_area():
|
||||
x, y = symbols('x, y')
|
||||
e = Ellipse(Point(0, 0), 5, 4)
|
||||
I_yy = 2*4*integrate(sqrt(25 - x**2)*x**2, (x, -5, 5))/5
|
||||
I_xx = 2*5*integrate(sqrt(16 - y**2)*y**2, (y, -4, 4))/4
|
||||
Y = 3*sqrt(1 - x**2/5**2)
|
||||
I_xy = integrate(integrate(y, (y, -Y, Y))*x, (x, -5, 5))
|
||||
assert I_yy == e.second_moment_of_area()[1]
|
||||
assert I_xx == e.second_moment_of_area()[0]
|
||||
assert I_xy == e.second_moment_of_area()[2]
|
||||
#checking for other point
|
||||
t1 = e.second_moment_of_area(Point(6,5))
|
||||
t2 = (580*pi, 845*pi, 600*pi)
|
||||
assert t1==t2
|
||||
|
||||
|
||||
def test_section_modulus_and_polar_second_moment_of_area():
|
||||
d = Symbol('d', positive=True)
|
||||
c = Circle((3, 7), 8)
|
||||
assert c.polar_second_moment_of_area() == 2048*pi
|
||||
assert c.section_modulus() == (128*pi, 128*pi)
|
||||
c = Circle((2, 9), d/2)
|
||||
assert c.polar_second_moment_of_area() == pi*d**3*Abs(d)/64 + pi*d*Abs(d)**3/64
|
||||
assert c.section_modulus() == (pi*d**3/S(32), pi*d**3/S(32))
|
||||
|
||||
a, b = symbols('a, b', positive=True)
|
||||
e = Ellipse((4, 6), a, b)
|
||||
assert e.section_modulus() == (pi*a*b**2/S(4), pi*a**2*b/S(4))
|
||||
assert e.polar_second_moment_of_area() == pi*a**3*b/S(4) + pi*a*b**3/S(4)
|
||||
e = e.rotate(pi/2) # no change in polar and section modulus
|
||||
assert e.section_modulus() == (pi*a**2*b/S(4), pi*a*b**2/S(4))
|
||||
assert e.polar_second_moment_of_area() == pi*a**3*b/S(4) + pi*a*b**3/S(4)
|
||||
|
||||
e = Ellipse((a, b), 2, 6)
|
||||
assert e.section_modulus() == (18*pi, 6*pi)
|
||||
assert e.polar_second_moment_of_area() == 120*pi
|
||||
|
||||
e = Ellipse(Point(0, 0), 2, 2)
|
||||
assert e.section_modulus() == (2*pi, 2*pi)
|
||||
assert e.section_modulus(Point(2, 2)) == (2*pi, 2*pi)
|
||||
assert e.section_modulus((2, 2)) == (2*pi, 2*pi)
|
||||
|
||||
|
||||
def test_circumference():
|
||||
M = Symbol('M')
|
||||
m = Symbol('m')
|
||||
assert Ellipse(Point(0, 0), M, m).circumference == 4 * M * elliptic_e((M ** 2 - m ** 2) / M**2)
|
||||
|
||||
assert Ellipse(Point(0, 0), 5, 4).circumference == 20 * elliptic_e(S(9) / 25)
|
||||
|
||||
# circle
|
||||
assert Ellipse(None, 1, None, 0).circumference == 2*pi
|
||||
|
||||
# test numerically
|
||||
assert abs(Ellipse(None, hradius=5, vradius=3).circumference.evalf(16) - 25.52699886339813) < 1e-10
|
||||
|
||||
|
||||
def test_issue_15259():
|
||||
assert Circle((1, 2), 0) == Point(1, 2)
|
||||
|
||||
|
||||
def test_issue_15797_equals():
|
||||
Ri = 0.024127189424130748
|
||||
Ci = (0.0864931002830291, 0.0819863295239654)
|
||||
A = Point(0, 0.0578591400998346)
|
||||
c = Circle(Ci, Ri) # evaluated
|
||||
assert c.is_tangent(c.tangent_lines(A)[0]) == True
|
||||
assert c.center.x.is_Rational
|
||||
assert c.center.y.is_Rational
|
||||
assert c.radius.is_Rational
|
||||
u = Circle(Ci, Ri, evaluate=False) # unevaluated
|
||||
assert u.center.x.is_Float
|
||||
assert u.center.y.is_Float
|
||||
assert u.radius.is_Float
|
||||
|
||||
|
||||
def test_auxiliary_circle():
|
||||
x, y, a, b = symbols('x y a b')
|
||||
e = Ellipse((x, y), a, b)
|
||||
# the general result
|
||||
assert e.auxiliary_circle() == Circle((x, y), Max(a, b))
|
||||
# a special case where Ellipse is a Circle
|
||||
assert Circle((3, 4), 8).auxiliary_circle() == Circle((3, 4), 8)
|
||||
|
||||
|
||||
def test_director_circle():
|
||||
x, y, a, b = symbols('x y a b')
|
||||
e = Ellipse((x, y), a, b)
|
||||
# the general result
|
||||
assert e.director_circle() == Circle((x, y), sqrt(a**2 + b**2))
|
||||
# a special case where Ellipse is a Circle
|
||||
assert Circle((3, 4), 8).director_circle() == Circle((3, 4), 8*sqrt(2))
|
||||
|
||||
|
||||
def test_evolute():
|
||||
#ellipse centered at h,k
|
||||
x, y, h, k = symbols('x y h k',real = True)
|
||||
a, b = symbols('a b')
|
||||
e = Ellipse(Point(h, k), a, b)
|
||||
t1 = (e.hradius*(x - e.center.x))**Rational(2, 3)
|
||||
t2 = (e.vradius*(y - e.center.y))**Rational(2, 3)
|
||||
E = t1 + t2 - (e.hradius**2 - e.vradius**2)**Rational(2, 3)
|
||||
assert e.evolute() == E
|
||||
#Numerical Example
|
||||
e = Ellipse(Point(1, 1), 6, 3)
|
||||
t1 = (6*(x - 1))**Rational(2, 3)
|
||||
t2 = (3*(y - 1))**Rational(2, 3)
|
||||
E = t1 + t2 - (27)**Rational(2, 3)
|
||||
assert e.evolute() == E
|
||||
|
||||
|
||||
def test_svg():
|
||||
e1 = Ellipse(Point(1, 0), 3, 2)
|
||||
assert e1._svg(2, "#FFAAFF") == '<ellipse fill="#FFAAFF" stroke="#555555" stroke-width="4.0" opacity="0.6" cx="1.00000000000000" cy="0" rx="3.00000000000000" ry="2.00000000000000"/>'
|
||||
@@ -0,0 +1,120 @@
|
||||
from sympy.core.numbers import (Rational, pi)
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import Symbol
|
||||
from sympy.geometry import (Circle, Ellipse, Point, Line, Parabola,
|
||||
Polygon, Ray, RegularPolygon, Segment, Triangle, Plane, Curve)
|
||||
from sympy.geometry.entity import scale, GeometryEntity
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
|
||||
def test_entity():
|
||||
x = Symbol('x', real=True)
|
||||
y = Symbol('y', real=True)
|
||||
|
||||
assert GeometryEntity(x, y) in GeometryEntity(x, y)
|
||||
raises(NotImplementedError, lambda: Point(0, 0) in GeometryEntity(x, y))
|
||||
|
||||
assert GeometryEntity(x, y) == GeometryEntity(x, y)
|
||||
assert GeometryEntity(x, y).equals(GeometryEntity(x, y))
|
||||
|
||||
c = Circle((0, 0), 5)
|
||||
assert GeometryEntity.encloses(c, Point(0, 0))
|
||||
assert GeometryEntity.encloses(c, Segment((0, 0), (1, 1)))
|
||||
assert GeometryEntity.encloses(c, Line((0, 0), (1, 1))) is False
|
||||
assert GeometryEntity.encloses(c, Circle((0, 0), 4))
|
||||
assert GeometryEntity.encloses(c, Polygon(Point(0, 0), Point(1, 0), Point(0, 1)))
|
||||
assert GeometryEntity.encloses(c, RegularPolygon(Point(8, 8), 1, 3)) is False
|
||||
|
||||
|
||||
def test_svg():
|
||||
a = Symbol('a')
|
||||
b = Symbol('b')
|
||||
d = Symbol('d')
|
||||
|
||||
entity = Circle(Point(a, b), d)
|
||||
assert entity._repr_svg_() is None
|
||||
|
||||
entity = Circle(Point(0, 0), S.Infinity)
|
||||
assert entity._repr_svg_() is None
|
||||
|
||||
|
||||
def test_subs():
|
||||
x = Symbol('x', real=True)
|
||||
y = Symbol('y', real=True)
|
||||
p = Point(x, 2)
|
||||
q = Point(1, 1)
|
||||
r = Point(3, 4)
|
||||
for o in [p,
|
||||
Segment(p, q),
|
||||
Ray(p, q),
|
||||
Line(p, q),
|
||||
Triangle(p, q, r),
|
||||
RegularPolygon(p, 3, 6),
|
||||
Polygon(p, q, r, Point(5, 4)),
|
||||
Circle(p, 3),
|
||||
Ellipse(p, 3, 4)]:
|
||||
assert 'y' in str(o.subs(x, y))
|
||||
assert p.subs({x: 1}) == Point(1, 2)
|
||||
assert Point(1, 2).subs(Point(1, 2), Point(3, 4)) == Point(3, 4)
|
||||
assert Point(1, 2).subs((1, 2), Point(3, 4)) == Point(3, 4)
|
||||
assert Point(1, 2).subs(Point(1, 2), Point(3, 4)) == Point(3, 4)
|
||||
assert Point(1, 2).subs({(1, 2)}) == Point(2, 2)
|
||||
raises(ValueError, lambda: Point(1, 2).subs(1))
|
||||
raises(TypeError, lambda: Point(1, 1).subs((Point(1, 1), Point(1,
|
||||
2)), 1, 2))
|
||||
|
||||
|
||||
def test_transform():
|
||||
assert scale(1, 2, (3, 4)).tolist() == \
|
||||
[[1, 0, 0], [0, 2, 0], [0, -4, 1]]
|
||||
|
||||
|
||||
def test_reflect_entity_overrides():
|
||||
x = Symbol('x', real=True)
|
||||
y = Symbol('y', real=True)
|
||||
b = Symbol('b')
|
||||
m = Symbol('m')
|
||||
l = Line((0, b), slope=m)
|
||||
p = Point(x, y)
|
||||
r = p.reflect(l)
|
||||
c = Circle((x, y), 3)
|
||||
cr = c.reflect(l)
|
||||
assert cr == Circle(r, -3)
|
||||
assert c.area == -cr.area
|
||||
|
||||
pent = RegularPolygon((1, 2), 1, 5)
|
||||
slope = S.ComplexInfinity
|
||||
while slope is S.ComplexInfinity:
|
||||
slope = Rational(*(x._random()/2).as_real_imag())
|
||||
l = Line(pent.vertices[1], slope=slope)
|
||||
rpent = pent.reflect(l)
|
||||
assert rpent.center == pent.center.reflect(l)
|
||||
rvert = [i.reflect(l) for i in pent.vertices]
|
||||
for v in rpent.vertices:
|
||||
for i in range(len(rvert)):
|
||||
ri = rvert[i]
|
||||
if ri.equals(v):
|
||||
rvert.remove(ri)
|
||||
break
|
||||
assert not rvert
|
||||
assert pent.area.equals(-rpent.area)
|
||||
|
||||
|
||||
def test_geometry_EvalfMixin():
|
||||
x = pi
|
||||
t = Symbol('t')
|
||||
for g in [
|
||||
Point(x, x),
|
||||
Plane(Point(0, x, 0), (0, 0, x)),
|
||||
Curve((x*t, x), (t, 0, x)),
|
||||
Ellipse((x, x), x, -x),
|
||||
Circle((x, x), x),
|
||||
Line((0, x), (x, 0)),
|
||||
Segment((0, x), (x, 0)),
|
||||
Ray((0, x), (x, 0)),
|
||||
Parabola((0, x), Line((-x, 0), (x, 0))),
|
||||
Polygon((0, 0), (0, x), (x, 0), (x, x)),
|
||||
RegularPolygon((0, x), x, 4, x),
|
||||
Triangle((0, 0), (x, 0), (x, x)),
|
||||
]:
|
||||
assert str(g).replace('pi', '3.1') == str(g.n(2))
|
||||
@@ -0,0 +1,38 @@
|
||||
from sympy.core.numbers import Rational
|
||||
from sympy.core.singleton import S
|
||||
from sympy.geometry import Circle, Line, Point, Polygon, Segment
|
||||
from sympy.sets import FiniteSet, Union, Intersection, EmptySet
|
||||
|
||||
|
||||
def test_booleans():
|
||||
""" test basic unions and intersections """
|
||||
half = S.Half
|
||||
|
||||
p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)])
|
||||
p5, p6, p7 = map(Point, [(3, 2), (1, -1), (0, 2)])
|
||||
l1 = Line(Point(0,0), Point(1,1))
|
||||
l2 = Line(Point(half, half), Point(5,5))
|
||||
l3 = Line(p2, p3)
|
||||
l4 = Line(p3, p4)
|
||||
poly1 = Polygon(p1, p2, p3, p4)
|
||||
poly2 = Polygon(p5, p6, p7)
|
||||
poly3 = Polygon(p1, p2, p5)
|
||||
assert Union(l1, l2).equals(l1)
|
||||
assert Intersection(l1, l2).equals(l1)
|
||||
assert Intersection(l1, l4) == FiniteSet(Point(1,1))
|
||||
assert Intersection(Union(l1, l4), l3) == FiniteSet(Point(Rational(-1, 3), Rational(-1, 3)), Point(5, 1))
|
||||
assert Intersection(l1, FiniteSet(Point(7,-7))) == EmptySet
|
||||
assert Intersection(Circle(Point(0,0), 3), Line(p1,p2)) == FiniteSet(Point(-3,0), Point(3,0))
|
||||
assert Intersection(l1, FiniteSet(p1)) == FiniteSet(p1)
|
||||
assert Union(l1, FiniteSet(p1)) == l1
|
||||
|
||||
fs = FiniteSet(Point(Rational(1, 3), 1), Point(Rational(2, 3), 0), Point(Rational(9, 5), Rational(1, 5)), Point(Rational(7, 3), 1))
|
||||
# test the intersection of polygons
|
||||
assert Intersection(poly1, poly2) == fs
|
||||
# make sure if we union polygons with subsets, the subsets go away
|
||||
assert Union(poly1, poly2, fs) == Union(poly1, poly2)
|
||||
# make sure that if we union with a FiniteSet that isn't a subset,
|
||||
# that the points in the intersection stop being listed
|
||||
assert Union(poly1, FiniteSet(Point(0,0), Point(3,5))) == Union(poly1, FiniteSet(Point(3,5)))
|
||||
# intersect two polygons that share an edge
|
||||
assert Intersection(poly1, poly3) == Union(FiniteSet(Point(Rational(3, 2), 1), Point(2, 1)), Segment(Point(0, 0), Point(1, 0)))
|
||||
@@ -0,0 +1,861 @@
|
||||
from sympy.core.numbers import (Float, Rational, oo, pi)
|
||||
from sympy.core.relational import Eq
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import (Symbol, symbols)
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.functions.elementary.trigonometric import (acos, cos, sin)
|
||||
from sympy.sets import EmptySet
|
||||
from sympy.simplify.simplify import simplify
|
||||
from sympy.functions.elementary.trigonometric import tan
|
||||
from sympy.geometry import (Circle, GeometryError, Line, Point, Ray,
|
||||
Segment, Triangle, intersection, Point3D, Line3D, Ray3D, Segment3D,
|
||||
Point2D, Line2D, Plane)
|
||||
from sympy.geometry.line import Undecidable
|
||||
from sympy.geometry.polygon import _asa as asa
|
||||
from sympy.utilities.iterables import cartes
|
||||
from sympy.testing.pytest import raises, warns
|
||||
|
||||
|
||||
x = Symbol('x', real=True)
|
||||
y = Symbol('y', real=True)
|
||||
z = Symbol('z', real=True)
|
||||
k = Symbol('k', real=True)
|
||||
x1 = Symbol('x1', real=True)
|
||||
y1 = Symbol('y1', real=True)
|
||||
t = Symbol('t', real=True)
|
||||
a, b = symbols('a,b', real=True)
|
||||
m = symbols('m', real=True)
|
||||
|
||||
|
||||
def test_object_from_equation():
|
||||
from sympy.abc import x, y, a, b
|
||||
assert Line(3*x + y + 18) == Line2D(Point2D(0, -18), Point2D(1, -21))
|
||||
assert Line(3*x + 5 * y + 1) == Line2D(
|
||||
Point2D(0, Rational(-1, 5)), Point2D(1, Rational(-4, 5)))
|
||||
assert Line(3*a + b + 18, x="a", y="b") == Line2D(
|
||||
Point2D(0, -18), Point2D(1, -21))
|
||||
assert Line(3*x + y) == Line2D(Point2D(0, 0), Point2D(1, -3))
|
||||
assert Line(x + y) == Line2D(Point2D(0, 0), Point2D(1, -1))
|
||||
assert Line(Eq(3*a + b, -18), x="a", y=b) == Line2D(
|
||||
Point2D(0, -18), Point2D(1, -21))
|
||||
# issue 22361
|
||||
assert Line(x - 1) == Line2D(Point2D(1, 0), Point2D(1, 1))
|
||||
assert Line(2*x - 2, y=x) == Line2D(Point2D(0, 1), Point2D(1, 1))
|
||||
assert Line(y) == Line2D(Point2D(0, 0), Point2D(1, 0))
|
||||
assert Line(2*y, x=y) == Line2D(Point2D(0, 0), Point2D(0, 1))
|
||||
assert Line(y, x=y) == Line2D(Point2D(0, 0), Point2D(0, 1))
|
||||
raises(ValueError, lambda: Line(x / y))
|
||||
raises(ValueError, lambda: Line(a / b, x='a', y='b'))
|
||||
raises(ValueError, lambda: Line(y / x))
|
||||
raises(ValueError, lambda: Line(b / a, x='a', y='b'))
|
||||
raises(ValueError, lambda: Line((x + 1)**2 + y))
|
||||
|
||||
|
||||
def feq(a, b):
|
||||
"""Test if two floating point values are 'equal'."""
|
||||
t_float = Float("1.0E-10")
|
||||
return -t_float < a - b < t_float
|
||||
|
||||
|
||||
def test_angle_between():
|
||||
a = Point(1, 2, 3, 4)
|
||||
b = a.orthogonal_direction
|
||||
o = a.origin
|
||||
assert feq(Line.angle_between(Line(Point(0, 0), Point(1, 1)),
|
||||
Line(Point(0, 0), Point(5, 0))).evalf(), pi.evalf() / 4)
|
||||
assert Line(a, o).angle_between(Line(b, o)) == pi / 2
|
||||
z = Point3D(0, 0, 0)
|
||||
assert Line3D.angle_between(Line3D(z, Point3D(1, 1, 1)),
|
||||
Line3D(z, Point3D(5, 0, 0))) == acos(sqrt(3) / 3)
|
||||
# direction of points is used to determine angle
|
||||
assert Line3D.angle_between(Line3D(z, Point3D(1, 1, 1)),
|
||||
Line3D(Point3D(5, 0, 0), z)) == acos(-sqrt(3) / 3)
|
||||
|
||||
|
||||
def test_closing_angle():
|
||||
a = Ray((0, 0), angle=0)
|
||||
b = Ray((1, 2), angle=pi/2)
|
||||
assert a.closing_angle(b) == -pi/2
|
||||
assert b.closing_angle(a) == pi/2
|
||||
assert a.closing_angle(a) == 0
|
||||
|
||||
|
||||
def test_smallest_angle():
|
||||
a = Line(Point(1, 1), Point(1, 2))
|
||||
b = Line(Point(1, 1),Point(2, 3))
|
||||
assert a.smallest_angle_between(b) == acos(2*sqrt(5)/5)
|
||||
|
||||
|
||||
def test_svg():
|
||||
a = Line(Point(1, 1),Point(1, 2))
|
||||
assert a._svg() == '<path fill-rule="evenodd" fill="#66cc99" stroke="#555555" stroke-width="2.0" opacity="0.6" d="M 1.00000000000000,1.00000000000000 L 1.00000000000000,2.00000000000000" marker-start="url(#markerReverseArrow)" marker-end="url(#markerArrow)"/>'
|
||||
a = Segment(Point(1, 0),Point(1, 1))
|
||||
assert a._svg() == '<path fill-rule="evenodd" fill="#66cc99" stroke="#555555" stroke-width="2.0" opacity="0.6" d="M 1.00000000000000,0 L 1.00000000000000,1.00000000000000" />'
|
||||
a = Ray(Point(2, 3), Point(3, 5))
|
||||
assert a._svg() == '<path fill-rule="evenodd" fill="#66cc99" stroke="#555555" stroke-width="2.0" opacity="0.6" d="M 2.00000000000000,3.00000000000000 L 3.00000000000000,5.00000000000000" marker-start="url(#markerCircle)" marker-end="url(#markerArrow)"/>'
|
||||
|
||||
|
||||
def test_arbitrary_point():
|
||||
l1 = Line3D(Point3D(0, 0, 0), Point3D(1, 1, 1))
|
||||
l2 = Line(Point(x1, x1), Point(y1, y1))
|
||||
assert l2.arbitrary_point() in l2
|
||||
assert Ray((1, 1), angle=pi / 4).arbitrary_point() == \
|
||||
Point(t + 1, t + 1)
|
||||
assert Segment((1, 1), (2, 3)).arbitrary_point() == Point(1 + t, 1 + 2 * t)
|
||||
assert l1.perpendicular_segment(l1.arbitrary_point()) == l1.arbitrary_point()
|
||||
assert Ray3D((1, 1, 1), direction_ratio=[1, 2, 3]).arbitrary_point() == \
|
||||
Point3D(t + 1, 2 * t + 1, 3 * t + 1)
|
||||
assert Segment3D(Point3D(0, 0, 0), Point3D(1, 1, 1)).midpoint == \
|
||||
Point3D(S.Half, S.Half, S.Half)
|
||||
assert Segment3D(Point3D(x1, x1, x1), Point3D(y1, y1, y1)).length == sqrt(3) * sqrt((x1 - y1) ** 2)
|
||||
assert Segment3D((1, 1, 1), (2, 3, 4)).arbitrary_point() == \
|
||||
Point3D(t + 1, 2 * t + 1, 3 * t + 1)
|
||||
raises(ValueError, (lambda: Line((x, 1), (2, 3)).arbitrary_point(x)))
|
||||
|
||||
|
||||
def test_are_concurrent_2d():
|
||||
l1 = Line(Point(0, 0), Point(1, 1))
|
||||
l2 = Line(Point(x1, x1), Point(x1, 1 + x1))
|
||||
assert Line.are_concurrent(l1) is False
|
||||
assert Line.are_concurrent(l1, l2)
|
||||
assert Line.are_concurrent(l1, l1, l1, l2)
|
||||
assert Line.are_concurrent(l1, l2, Line(Point(5, x1), Point(Rational(-3, 5), x1)))
|
||||
assert Line.are_concurrent(l1, Line(Point(0, 0), Point(-x1, x1)), l2) is False
|
||||
|
||||
|
||||
def test_are_concurrent_3d():
|
||||
p1 = Point3D(0, 0, 0)
|
||||
l1 = Line(p1, Point3D(1, 1, 1))
|
||||
parallel_1 = Line3D(Point3D(0, 0, 0), Point3D(1, 0, 0))
|
||||
parallel_2 = Line3D(Point3D(0, 1, 0), Point3D(1, 1, 0))
|
||||
assert Line3D.are_concurrent(l1) is False
|
||||
assert Line3D.are_concurrent(l1, Line(Point3D(x1, x1, x1), Point3D(y1, y1, y1))) is False
|
||||
assert Line3D.are_concurrent(l1, Line3D(p1, Point3D(x1, x1, x1)),
|
||||
Line(Point3D(x1, x1, x1), Point3D(x1, 1 + x1, 1))) is True
|
||||
assert Line3D.are_concurrent(parallel_1, parallel_2) is False
|
||||
|
||||
|
||||
def test_arguments():
|
||||
"""Functions accepting `Point` objects in `geometry`
|
||||
should also accept tuples, lists, and generators and
|
||||
automatically convert them to points."""
|
||||
from sympy.utilities.iterables import subsets
|
||||
|
||||
singles2d = ((1, 2), [1, 3], Point(1, 5))
|
||||
doubles2d = subsets(singles2d, 2)
|
||||
l2d = Line(Point2D(1, 2), Point2D(2, 3))
|
||||
singles3d = ((1, 2, 3), [1, 2, 4], Point(1, 2, 6))
|
||||
doubles3d = subsets(singles3d, 2)
|
||||
l3d = Line(Point3D(1, 2, 3), Point3D(1, 1, 2))
|
||||
singles4d = ((1, 2, 3, 4), [1, 2, 3, 5], Point(1, 2, 3, 7))
|
||||
doubles4d = subsets(singles4d, 2)
|
||||
l4d = Line(Point(1, 2, 3, 4), Point(2, 2, 2, 2))
|
||||
# test 2D
|
||||
test_single = ['contains', 'distance', 'equals', 'parallel_line', 'perpendicular_line', 'perpendicular_segment',
|
||||
'projection', 'intersection']
|
||||
for p in doubles2d:
|
||||
Line2D(*p)
|
||||
for func in test_single:
|
||||
for p in singles2d:
|
||||
getattr(l2d, func)(p)
|
||||
# test 3D
|
||||
for p in doubles3d:
|
||||
Line3D(*p)
|
||||
for func in test_single:
|
||||
for p in singles3d:
|
||||
getattr(l3d, func)(p)
|
||||
# test 4D
|
||||
for p in doubles4d:
|
||||
Line(*p)
|
||||
for func in test_single:
|
||||
for p in singles4d:
|
||||
getattr(l4d, func)(p)
|
||||
|
||||
|
||||
def test_basic_properties_2d():
|
||||
p1 = Point(0, 0)
|
||||
p2 = Point(1, 1)
|
||||
p10 = Point(2000, 2000)
|
||||
p_r3 = Ray(p1, p2).random_point()
|
||||
p_r4 = Ray(p2, p1).random_point()
|
||||
|
||||
l1 = Line(p1, p2)
|
||||
l3 = Line(Point(x1, x1), Point(x1, 1 + x1))
|
||||
l4 = Line(p1, Point(1, 0))
|
||||
|
||||
r1 = Ray(p1, Point(0, 1))
|
||||
r2 = Ray(Point(0, 1), p1)
|
||||
|
||||
s1 = Segment(p1, p10)
|
||||
p_s1 = s1.random_point()
|
||||
|
||||
assert Line((1, 1), slope=1) == Line((1, 1), (2, 2))
|
||||
assert Line((1, 1), slope=oo) == Line((1, 1), (1, 2))
|
||||
assert Line((1, 1), slope=oo).bounds == (1, 1, 1, 2)
|
||||
assert Line((1, 1), slope=-oo) == Line((1, 1), (1, 2))
|
||||
assert Line(p1, p2).scale(2, 1) == Line(p1, Point(2, 1))
|
||||
assert Line(p1, p2) == Line(p1, p2)
|
||||
assert Line(p1, p2) != Line(p2, p1)
|
||||
assert l1 != Line(Point(x1, x1), Point(y1, y1))
|
||||
assert l1 != l3
|
||||
assert Line(p1, p10) != Line(p10, p1)
|
||||
assert Line(p1, p10) != p1
|
||||
assert p1 in l1 # is p1 on the line l1?
|
||||
assert p1 not in l3
|
||||
assert s1 in Line(p1, p10)
|
||||
assert Ray(Point(0, 0), Point(0, 1)) in Ray(Point(0, 0), Point(0, 2))
|
||||
assert Ray(Point(0, 0), Point(0, 2)) in Ray(Point(0, 0), Point(0, 1))
|
||||
assert Ray(Point(0, 0), Point(0, 2)).xdirection == S.Zero
|
||||
assert Ray(Point(0, 0), Point(1, 2)).xdirection == S.Infinity
|
||||
assert Ray(Point(0, 0), Point(-1, 2)).xdirection == S.NegativeInfinity
|
||||
assert Ray(Point(0, 0), Point(2, 0)).ydirection == S.Zero
|
||||
assert Ray(Point(0, 0), Point(2, 2)).ydirection == S.Infinity
|
||||
assert Ray(Point(0, 0), Point(2, -2)).ydirection == S.NegativeInfinity
|
||||
assert (r1 in s1) is False
|
||||
assert Segment(p1, p2) in s1
|
||||
assert Ray(Point(x1, x1), Point(x1, 1 + x1)) != Ray(p1, Point(-1, 5))
|
||||
assert Segment(p1, p2).midpoint == Point(S.Half, S.Half)
|
||||
assert Segment(p1, Point(-x1, x1)).length == sqrt(2 * (x1 ** 2))
|
||||
|
||||
assert l1.slope == 1
|
||||
assert l3.slope is oo
|
||||
assert l4.slope == 0
|
||||
assert Line(p1, Point(0, 1)).slope is oo
|
||||
assert Line(r1.source, r1.random_point()).slope == r1.slope
|
||||
assert Line(r2.source, r2.random_point()).slope == r2.slope
|
||||
assert Segment(Point(0, -1), Segment(p1, Point(0, 1)).random_point()).slope == Segment(p1, Point(0, 1)).slope
|
||||
|
||||
assert l4.coefficients == (0, 1, 0)
|
||||
assert Line((-x, x), (-x + 1, x - 1)).coefficients == (1, 1, 0)
|
||||
assert Line(p1, Point(0, 1)).coefficients == (1, 0, 0)
|
||||
# issue 7963
|
||||
r = Ray((0, 0), angle=x)
|
||||
assert r.subs(x, 3 * pi / 4) == Ray((0, 0), (-1, 1))
|
||||
assert r.subs(x, 5 * pi / 4) == Ray((0, 0), (-1, -1))
|
||||
assert r.subs(x, -pi / 4) == Ray((0, 0), (1, -1))
|
||||
assert r.subs(x, pi / 2) == Ray((0, 0), (0, 1))
|
||||
assert r.subs(x, -pi / 2) == Ray((0, 0), (0, -1))
|
||||
|
||||
for ind in range(0, 5):
|
||||
assert l3.random_point() in l3
|
||||
|
||||
assert p_r3.x >= p1.x and p_r3.y >= p1.y
|
||||
assert p_r4.x <= p2.x and p_r4.y <= p2.y
|
||||
assert p1.x <= p_s1.x <= p10.x and p1.y <= p_s1.y <= p10.y
|
||||
assert hash(s1) != hash(Segment(p10, p1))
|
||||
|
||||
assert s1.plot_interval() == [t, 0, 1]
|
||||
assert Line(p1, p10).plot_interval() == [t, -5, 5]
|
||||
assert Ray((0, 0), angle=pi / 4).plot_interval() == [t, 0, 10]
|
||||
|
||||
|
||||
def test_basic_properties_3d():
|
||||
p1 = Point3D(0, 0, 0)
|
||||
p2 = Point3D(1, 1, 1)
|
||||
p3 = Point3D(x1, x1, x1)
|
||||
p5 = Point3D(x1, 1 + x1, 1)
|
||||
|
||||
l1 = Line3D(p1, p2)
|
||||
l3 = Line3D(p3, p5)
|
||||
|
||||
r1 = Ray3D(p1, Point3D(-1, 5, 0))
|
||||
r3 = Ray3D(p1, p2)
|
||||
|
||||
s1 = Segment3D(p1, p2)
|
||||
|
||||
assert Line3D((1, 1, 1), direction_ratio=[2, 3, 4]) == Line3D(Point3D(1, 1, 1), Point3D(3, 4, 5))
|
||||
assert Line3D((1, 1, 1), direction_ratio=[1, 5, 7]) == Line3D(Point3D(1, 1, 1), Point3D(2, 6, 8))
|
||||
assert Line3D((1, 1, 1), direction_ratio=[1, 2, 3]) == Line3D(Point3D(1, 1, 1), Point3D(2, 3, 4))
|
||||
assert Line3D(Point3D(0, 0, 0), Point3D(1, 0, 0)).direction_cosine == [1, 0, 0]
|
||||
assert Line3D(Line3D(p1, Point3D(0, 1, 0))) == Line3D(p1, Point3D(0, 1, 0))
|
||||
assert Ray3D(Line3D(Point3D(0, 0, 0), Point3D(1, 0, 0))) == Ray3D(p1, Point3D(1, 0, 0))
|
||||
assert Line3D(p1, p2) != Line3D(p2, p1)
|
||||
assert l1 != l3
|
||||
assert l1 != Line3D(p3, Point3D(y1, y1, y1))
|
||||
assert r3 != r1
|
||||
assert Ray3D(Point3D(0, 0, 0), Point3D(1, 1, 1)) in Ray3D(Point3D(0, 0, 0), Point3D(2, 2, 2))
|
||||
assert Ray3D(Point3D(0, 0, 0), Point3D(2, 2, 2)) in Ray3D(Point3D(0, 0, 0), Point3D(1, 1, 1))
|
||||
assert Ray3D(Point3D(0, 0, 0), Point3D(2, 2, 2)).xdirection == S.Infinity
|
||||
assert Ray3D(Point3D(0, 0, 0), Point3D(2, 2, 2)).ydirection == S.Infinity
|
||||
assert Ray3D(Point3D(0, 0, 0), Point3D(2, 2, 2)).zdirection == S.Infinity
|
||||
assert Ray3D(Point3D(0, 0, 0), Point3D(-2, 2, 2)).xdirection == S.NegativeInfinity
|
||||
assert Ray3D(Point3D(0, 0, 0), Point3D(2, -2, 2)).ydirection == S.NegativeInfinity
|
||||
assert Ray3D(Point3D(0, 0, 0), Point3D(2, 2, -2)).zdirection == S.NegativeInfinity
|
||||
assert Ray3D(Point3D(0, 0, 0), Point3D(0, 2, 2)).xdirection == S.Zero
|
||||
assert Ray3D(Point3D(0, 0, 0), Point3D(2, 0, 2)).ydirection == S.Zero
|
||||
assert Ray3D(Point3D(0, 0, 0), Point3D(2, 2, 0)).zdirection == S.Zero
|
||||
assert p1 in l1
|
||||
assert p1 not in l3
|
||||
|
||||
assert l1.direction_ratio == [1, 1, 1]
|
||||
|
||||
assert s1.midpoint == Point3D(S.Half, S.Half, S.Half)
|
||||
# Test zdirection
|
||||
assert Ray3D(p1, Point3D(0, 0, -1)).zdirection is S.NegativeInfinity
|
||||
|
||||
|
||||
def test_contains():
|
||||
p1 = Point(0, 0)
|
||||
|
||||
r = Ray(p1, Point(4, 4))
|
||||
r1 = Ray3D(p1, Point3D(0, 0, -1))
|
||||
r2 = Ray3D(p1, Point3D(0, 1, 0))
|
||||
r3 = Ray3D(p1, Point3D(0, 0, 1))
|
||||
|
||||
l = Line(Point(0, 1), Point(3, 4))
|
||||
# Segment contains
|
||||
assert Point(0, (a + b) / 2) in Segment((0, a), (0, b))
|
||||
assert Point((a + b) / 2, 0) in Segment((a, 0), (b, 0))
|
||||
assert Point3D(0, 1, 0) in Segment3D((0, 1, 0), (0, 1, 0))
|
||||
assert Point3D(1, 0, 0) in Segment3D((1, 0, 0), (1, 0, 0))
|
||||
assert Segment3D(Point3D(0, 0, 0), Point3D(1, 0, 0)).contains([]) is True
|
||||
assert Segment3D(Point3D(0, 0, 0), Point3D(1, 0, 0)).contains(
|
||||
Segment3D(Point3D(2, 2, 2), Point3D(3, 2, 2))) is False
|
||||
# Line contains
|
||||
assert l.contains(Point(0, 1)) is True
|
||||
assert l.contains((0, 1)) is True
|
||||
assert l.contains((0, 0)) is False
|
||||
# Ray contains
|
||||
assert r.contains(p1) is True
|
||||
assert r.contains((1, 1)) is True
|
||||
assert r.contains((1, 3)) is False
|
||||
assert r.contains(Segment((1, 1), (2, 2))) is True
|
||||
assert r.contains(Segment((1, 2), (2, 5))) is False
|
||||
assert r.contains(Ray((2, 2), (3, 3))) is True
|
||||
assert r.contains(Ray((2, 2), (3, 5))) is False
|
||||
assert r1.contains(Segment3D(p1, Point3D(0, 0, -10))) is True
|
||||
assert r1.contains(Segment3D(Point3D(1, 1, 1), Point3D(2, 2, 2))) is False
|
||||
assert r2.contains(Point3D(0, 0, 0)) is True
|
||||
assert r3.contains(Point3D(0, 0, 0)) is True
|
||||
assert Ray3D(Point3D(1, 1, 1), Point3D(1, 0, 0)).contains([]) is False
|
||||
assert Line3D((0, 0, 0), (x, y, z)).contains((2 * x, 2 * y, 2 * z))
|
||||
with warns(UserWarning, test_stacklevel=False):
|
||||
assert Line3D(p1, Point3D(0, 1, 0)).contains(Point(1.0, 1.0)) is False
|
||||
|
||||
with warns(UserWarning, test_stacklevel=False):
|
||||
assert r3.contains(Point(1.0, 1.0)) is False
|
||||
|
||||
|
||||
def test_contains_nonreal_symbols():
|
||||
u, v, w, z = symbols('u, v, w, z')
|
||||
l = Segment(Point(u, w), Point(v, z))
|
||||
p = Point(u*Rational(2, 3) + v/3, w*Rational(2, 3) + z/3)
|
||||
assert l.contains(p)
|
||||
|
||||
|
||||
def test_distance_2d():
|
||||
p1 = Point(0, 0)
|
||||
p2 = Point(1, 1)
|
||||
half = S.Half
|
||||
|
||||
s1 = Segment(Point(0, 0), Point(1, 1))
|
||||
s2 = Segment(Point(half, half), Point(1, 0))
|
||||
|
||||
r = Ray(p1, p2)
|
||||
|
||||
assert s1.distance(Point(0, 0)) == 0
|
||||
assert s1.distance((0, 0)) == 0
|
||||
assert s2.distance(Point(0, 0)) == 2 ** half / 2
|
||||
assert s2.distance(Point(Rational(3) / 2, Rational(3) / 2)) == 2 ** half
|
||||
assert Line(p1, p2).distance(Point(-1, 1)) == sqrt(2)
|
||||
assert Line(p1, p2).distance(Point(1, -1)) == sqrt(2)
|
||||
assert Line(p1, p2).distance(Point(2, 2)) == 0
|
||||
assert Line(p1, p2).distance((-1, 1)) == sqrt(2)
|
||||
assert Line((0, 0), (0, 1)).distance(p1) == 0
|
||||
assert Line((0, 0), (0, 1)).distance(p2) == 1
|
||||
assert Line((0, 0), (1, 0)).distance(p1) == 0
|
||||
assert Line((0, 0), (1, 0)).distance(p2) == 1
|
||||
assert r.distance(Point(-1, -1)) == sqrt(2)
|
||||
assert r.distance(Point(1, 1)) == 0
|
||||
assert r.distance(Point(-1, 1)) == sqrt(2)
|
||||
assert Ray((1, 1), (2, 2)).distance(Point(1.5, 3)) == 3 * sqrt(2) / 4
|
||||
assert r.distance((1, 1)) == 0
|
||||
|
||||
|
||||
def test_dimension_normalization():
|
||||
with warns(UserWarning, test_stacklevel=False):
|
||||
assert Ray((1, 1), (2, 1, 2)) == Ray((1, 1, 0), (2, 1, 2))
|
||||
|
||||
|
||||
def test_distance_3d():
|
||||
p1, p2 = Point3D(0, 0, 0), Point3D(1, 1, 1)
|
||||
p3 = Point3D(Rational(3) / 2, Rational(3) / 2, Rational(3) / 2)
|
||||
|
||||
s1 = Segment3D(Point3D(0, 0, 0), Point3D(1, 1, 1))
|
||||
s2 = Segment3D(Point3D(S.Half, S.Half, S.Half), Point3D(1, 0, 1))
|
||||
|
||||
r = Ray3D(p1, p2)
|
||||
|
||||
assert s1.distance(p1) == 0
|
||||
assert s2.distance(p1) == sqrt(3) / 2
|
||||
assert s2.distance(p3) == 2 * sqrt(6) / 3
|
||||
assert s1.distance((0, 0, 0)) == 0
|
||||
assert s2.distance((0, 0, 0)) == sqrt(3) / 2
|
||||
assert s1.distance(p1) == 0
|
||||
assert s2.distance(p1) == sqrt(3) / 2
|
||||
assert s2.distance(p3) == 2 * sqrt(6) / 3
|
||||
assert s1.distance((0, 0, 0)) == 0
|
||||
assert s2.distance((0, 0, 0)) == sqrt(3) / 2
|
||||
# Line to point
|
||||
assert Line3D(p1, p2).distance(Point3D(-1, 1, 1)) == 2 * sqrt(6) / 3
|
||||
assert Line3D(p1, p2).distance(Point3D(1, -1, 1)) == 2 * sqrt(6) / 3
|
||||
assert Line3D(p1, p2).distance(Point3D(2, 2, 2)) == 0
|
||||
assert Line3D(p1, p2).distance((2, 2, 2)) == 0
|
||||
assert Line3D(p1, p2).distance((1, -1, 1)) == 2 * sqrt(6) / 3
|
||||
assert Line3D((0, 0, 0), (0, 1, 0)).distance(p1) == 0
|
||||
assert Line3D((0, 0, 0), (0, 1, 0)).distance(p2) == sqrt(2)
|
||||
assert Line3D((0, 0, 0), (1, 0, 0)).distance(p1) == 0
|
||||
assert Line3D((0, 0, 0), (1, 0, 0)).distance(p2) == sqrt(2)
|
||||
# Line to line
|
||||
assert Line3D((0, 0, 0), (1, 0, 0)).distance(Line3D((0, 0, 0), (0, 1, 2))) == 0
|
||||
assert Line3D((0, 0, 0), (1, 0, 0)).distance(Line3D((0, 0, 0), (1, 0, 0))) == 0
|
||||
assert Line3D((0, 0, 0), (1, 0, 0)).distance(Line3D((10, 0, 0), (10, 1, 2))) == 0
|
||||
assert Line3D((0, 0, 0), (1, 0, 0)).distance(Line3D((0, 1, 0), (0, 1, 1))) == 1
|
||||
# Line to plane
|
||||
assert Line3D((0, 0, 0), (1, 0, 0)).distance(Plane((2, 0, 0), (0, 0, 1))) == 0
|
||||
assert Line3D((0, 0, 0), (1, 0, 0)).distance(Plane((0, 1, 0), (0, 1, 0))) == 1
|
||||
assert Line3D((0, 0, 0), (1, 0, 0)).distance(Plane((1, 1, 3), (1, 0, 0))) == 0
|
||||
# Ray to point
|
||||
assert r.distance(Point3D(-1, -1, -1)) == sqrt(3)
|
||||
assert r.distance(Point3D(1, 1, 1)) == 0
|
||||
assert r.distance((-1, -1, -1)) == sqrt(3)
|
||||
assert r.distance((1, 1, 1)) == 0
|
||||
assert Ray3D((0, 0, 0), (1, 1, 2)).distance((-1, -1, 2)) == 4 * sqrt(3) / 3
|
||||
assert Ray3D((1, 1, 1), (2, 2, 2)).distance(Point3D(1.5, -3, -1)) == Rational(9) / 2
|
||||
assert Ray3D((1, 1, 1), (2, 2, 2)).distance(Point3D(1.5, 3, 1)) == sqrt(78) / 6
|
||||
|
||||
|
||||
def test_equals():
|
||||
p1 = Point(0, 0)
|
||||
p2 = Point(1, 1)
|
||||
|
||||
l1 = Line(p1, p2)
|
||||
l2 = Line((0, 5), slope=m)
|
||||
l3 = Line(Point(x1, x1), Point(x1, 1 + x1))
|
||||
|
||||
assert l1.perpendicular_line(p1.args).equals(Line(Point(0, 0), Point(1, -1)))
|
||||
assert l1.perpendicular_line(p1).equals(Line(Point(0, 0), Point(1, -1)))
|
||||
assert Line(Point(x1, x1), Point(y1, y1)).parallel_line(Point(-x1, x1)). \
|
||||
equals(Line(Point(-x1, x1), Point(-y1, 2 * x1 - y1)))
|
||||
assert l3.parallel_line(p1.args).equals(Line(Point(0, 0), Point(0, -1)))
|
||||
assert l3.parallel_line(p1).equals(Line(Point(0, 0), Point(0, -1)))
|
||||
assert (l2.distance(Point(2, 3)) - 2 * abs(m + 1) / sqrt(m ** 2 + 1)).equals(0)
|
||||
assert Line3D(p1, Point3D(0, 1, 0)).equals(Point(1.0, 1.0)) is False
|
||||
assert Line3D(Point3D(0, 0, 0), Point3D(1, 0, 0)).equals(Line3D(Point3D(-5, 0, 0), Point3D(-1, 0, 0))) is True
|
||||
assert Line3D(Point3D(0, 0, 0), Point3D(1, 0, 0)).equals(Line3D(p1, Point3D(0, 1, 0))) is False
|
||||
assert Ray3D(p1, Point3D(0, 0, -1)).equals(Point(1.0, 1.0)) is False
|
||||
assert Ray3D(p1, Point3D(0, 0, -1)).equals(Ray3D(p1, Point3D(0, 0, -1))) is True
|
||||
assert Line3D((0, 0), (t, t)).perpendicular_line(Point(0, 1, 0)).equals(
|
||||
Line3D(Point3D(0, 1, 0), Point3D(S.Half, S.Half, 0)))
|
||||
assert Line3D((0, 0), (t, t)).perpendicular_segment(Point(0, 1, 0)).equals(Segment3D((0, 1), (S.Half, S.Half)))
|
||||
assert Line3D(p1, Point3D(0, 1, 0)).equals(Point(1.0, 1.0)) is False
|
||||
|
||||
|
||||
def test_equation():
|
||||
p1 = Point(0, 0)
|
||||
p2 = Point(1, 1)
|
||||
l1 = Line(p1, p2)
|
||||
l3 = Line(Point(x1, x1), Point(x1, 1 + x1))
|
||||
|
||||
assert simplify(l1.equation()) in (x - y, y - x)
|
||||
assert simplify(l3.equation()) in (x - x1, x1 - x)
|
||||
assert simplify(l1.equation()) in (x - y, y - x)
|
||||
assert simplify(l3.equation()) in (x - x1, x1 - x)
|
||||
|
||||
assert Line(p1, Point(1, 0)).equation(x=x, y=y) == y
|
||||
assert Line(p1, Point(0, 1)).equation() == x
|
||||
assert Line(Point(2, 0), Point(2, 1)).equation() == x - 2
|
||||
assert Line(p2, Point(2, 1)).equation() == y - 1
|
||||
|
||||
assert Line3D(Point(x1, x1, x1), Point(y1, y1, y1)
|
||||
).equation() == (-x + y, -x + z)
|
||||
assert Line3D(Point(1, 2, 3), Point(2, 3, 4)
|
||||
).equation() == (-x + y - 1, -x + z - 2)
|
||||
assert Line3D(Point(1, 2, 3), Point(1, 3, 4)
|
||||
).equation() == (x - 1, -y + z - 1)
|
||||
assert Line3D(Point(1, 2, 3), Point(2, 2, 4)
|
||||
).equation() == (y - 2, -x + z - 2)
|
||||
assert Line3D(Point(1, 2, 3), Point(2, 3, 3)
|
||||
).equation() == (-x + y - 1, z - 3)
|
||||
assert Line3D(Point(1, 2, 3), Point(1, 2, 4)
|
||||
).equation() == (x - 1, y - 2)
|
||||
assert Line3D(Point(1, 2, 3), Point(1, 3, 3)
|
||||
).equation() == (x - 1, z - 3)
|
||||
assert Line3D(Point(1, 2, 3), Point(2, 2, 3)
|
||||
).equation() == (y - 2, z - 3)
|
||||
|
||||
|
||||
def test_intersection_2d():
|
||||
p1 = Point(0, 0)
|
||||
p2 = Point(1, 1)
|
||||
p3 = Point(x1, x1)
|
||||
p4 = Point(y1, y1)
|
||||
|
||||
l1 = Line(p1, p2)
|
||||
l3 = Line(Point(0, 0), Point(3, 4))
|
||||
|
||||
r1 = Ray(Point(1, 1), Point(2, 2))
|
||||
r2 = Ray(Point(0, 0), Point(3, 4))
|
||||
r4 = Ray(p1, p2)
|
||||
r6 = Ray(Point(0, 1), Point(1, 2))
|
||||
r7 = Ray(Point(0.5, 0.5), Point(1, 1))
|
||||
|
||||
s1 = Segment(p1, p2)
|
||||
s2 = Segment(Point(0.25, 0.25), Point(0.5, 0.5))
|
||||
s3 = Segment(Point(0, 0), Point(3, 4))
|
||||
|
||||
assert intersection(l1, p1) == [p1]
|
||||
assert intersection(l1, Point(x1, 1 + x1)) == []
|
||||
assert intersection(l1, Line(p3, p4)) in [[l1], [Line(p3, p4)]]
|
||||
assert intersection(l1, l1.parallel_line(Point(x1, 1 + x1))) == []
|
||||
assert intersection(l3, l3) == [l3]
|
||||
assert intersection(l3, r2) == [r2]
|
||||
assert intersection(l3, s3) == [s3]
|
||||
assert intersection(s3, l3) == [s3]
|
||||
assert intersection(Segment(Point(-10, 10), Point(10, 10)), Segment(Point(-5, -5), Point(-5, 5))) == []
|
||||
assert intersection(r2, l3) == [r2]
|
||||
assert intersection(r1, Ray(Point(2, 2), Point(0, 0))) == [Segment(Point(1, 1), Point(2, 2))]
|
||||
assert intersection(r1, Ray(Point(1, 1), Point(-1, -1))) == [Point(1, 1)]
|
||||
assert intersection(r1, Segment(Point(0, 0), Point(2, 2))) == [Segment(Point(1, 1), Point(2, 2))]
|
||||
|
||||
assert r4.intersection(s2) == [s2]
|
||||
assert r4.intersection(Segment(Point(2, 3), Point(3, 4))) == []
|
||||
assert r4.intersection(Segment(Point(-1, -1), Point(0.5, 0.5))) == [Segment(p1, Point(0.5, 0.5))]
|
||||
assert r4.intersection(Ray(p2, p1)) == [s1]
|
||||
assert Ray(p2, p1).intersection(r6) == []
|
||||
assert r4.intersection(r7) == r7.intersection(r4) == [r7]
|
||||
assert Ray3D((0, 0), (3, 0)).intersection(Ray3D((1, 0), (3, 0))) == [Ray3D((1, 0), (3, 0))]
|
||||
assert Ray3D((1, 0), (3, 0)).intersection(Ray3D((0, 0), (3, 0))) == [Ray3D((1, 0), (3, 0))]
|
||||
assert Ray(Point(0, 0), Point(0, 4)).intersection(Ray(Point(0, 1), Point(0, -1))) == \
|
||||
[Segment(Point(0, 0), Point(0, 1))]
|
||||
|
||||
assert Segment3D((0, 0), (3, 0)).intersection(
|
||||
Segment3D((1, 0), (2, 0))) == [Segment3D((1, 0), (2, 0))]
|
||||
assert Segment3D((1, 0), (2, 0)).intersection(
|
||||
Segment3D((0, 0), (3, 0))) == [Segment3D((1, 0), (2, 0))]
|
||||
assert Segment3D((0, 0), (3, 0)).intersection(
|
||||
Segment3D((3, 0), (4, 0))) == [Point3D((3, 0))]
|
||||
assert Segment3D((0, 0), (3, 0)).intersection(
|
||||
Segment3D((2, 0), (5, 0))) == [Segment3D((2, 0), (3, 0))]
|
||||
assert Segment3D((0, 0), (3, 0)).intersection(
|
||||
Segment3D((-2, 0), (1, 0))) == [Segment3D((0, 0), (1, 0))]
|
||||
assert Segment3D((0, 0), (3, 0)).intersection(
|
||||
Segment3D((-2, 0), (0, 0))) == [Point3D(0, 0)]
|
||||
assert s1.intersection(Segment(Point(1, 1), Point(2, 2))) == [Point(1, 1)]
|
||||
assert s1.intersection(Segment(Point(0.5, 0.5), Point(1.5, 1.5))) == [Segment(Point(0.5, 0.5), p2)]
|
||||
assert s1.intersection(Segment(Point(4, 4), Point(5, 5))) == []
|
||||
assert s1.intersection(Segment(Point(-1, -1), p1)) == [p1]
|
||||
assert s1.intersection(Segment(Point(-1, -1), Point(0.5, 0.5))) == [Segment(p1, Point(0.5, 0.5))]
|
||||
assert s1.intersection(Line(Point(1, 0), Point(2, 1))) == []
|
||||
assert s1.intersection(s2) == [s2]
|
||||
assert s2.intersection(s1) == [s2]
|
||||
|
||||
assert asa(120, 8, 52) == \
|
||||
Triangle(
|
||||
Point(0, 0),
|
||||
Point(8, 0),
|
||||
Point(-4 * cos(19 * pi / 90) / sin(2 * pi / 45),
|
||||
4 * sqrt(3) * cos(19 * pi / 90) / sin(2 * pi / 45)))
|
||||
assert Line((0, 0), (1, 1)).intersection(Ray((1, 0), (1, 2))) == [Point(1, 1)]
|
||||
assert Line((0, 0), (1, 1)).intersection(Segment((1, 0), (1, 2))) == [Point(1, 1)]
|
||||
assert Ray((0, 0), (1, 1)).intersection(Ray((1, 0), (1, 2))) == [Point(1, 1)]
|
||||
assert Ray((0, 0), (1, 1)).intersection(Segment((1, 0), (1, 2))) == [Point(1, 1)]
|
||||
assert Ray((0, 0), (10, 10)).contains(Segment((1, 1), (2, 2))) is True
|
||||
assert Segment((1, 1), (2, 2)) in Line((0, 0), (10, 10))
|
||||
assert s1.intersection(Ray((1, 1), (4, 4))) == [Point(1, 1)]
|
||||
|
||||
# This test is disabled because it hangs after rref changes which simplify
|
||||
# intermediate results and return a different representation from when the
|
||||
# test was written.
|
||||
# # 16628 - this should be fast
|
||||
# p0 = Point2D(Rational(249, 5), Rational(497999, 10000))
|
||||
# p1 = Point2D((-58977084786*sqrt(405639795226) + 2030690077184193 +
|
||||
# 20112207807*sqrt(630547164901) + 99600*sqrt(255775022850776494562626))
|
||||
# /(2000*sqrt(255775022850776494562626) + 1991998000*sqrt(405639795226)
|
||||
# + 1991998000*sqrt(630547164901) + 1622561172902000),
|
||||
# (-498000*sqrt(255775022850776494562626) - 995999*sqrt(630547164901) +
|
||||
# 90004251917891999 +
|
||||
# 496005510002*sqrt(405639795226))/(10000*sqrt(255775022850776494562626)
|
||||
# + 9959990000*sqrt(405639795226) + 9959990000*sqrt(630547164901) +
|
||||
# 8112805864510000))
|
||||
# p2 = Point2D(Rational(497, 10), Rational(-497, 10))
|
||||
# p3 = Point2D(Rational(-497, 10), Rational(-497, 10))
|
||||
# l = Line(p0, p1)
|
||||
# s = Segment(p2, p3)
|
||||
# n = (-52673223862*sqrt(405639795226) - 15764156209307469 -
|
||||
# 9803028531*sqrt(630547164901) +
|
||||
# 33200*sqrt(255775022850776494562626))
|
||||
# d = sqrt(405639795226) + 315274080450 + 498000*sqrt(
|
||||
# 630547164901) + sqrt(255775022850776494562626)
|
||||
# assert intersection(l, s) == [
|
||||
# Point2D(n/d*Rational(3, 2000), Rational(-497, 10))]
|
||||
|
||||
|
||||
def test_line_intersection():
|
||||
# see also test_issue_11238 in test_matrices.py
|
||||
x0 = tan(pi*Rational(13, 45))
|
||||
x1 = sqrt(3)
|
||||
x2 = x0**2
|
||||
x, y = [8*x0/(x0 + x1), (24*x0 - 8*x1*x2)/(x2 - 3)]
|
||||
assert Line(Point(0, 0), Point(1, -sqrt(3))).contains(Point(x, y)) is True
|
||||
|
||||
|
||||
def test_intersection_3d():
|
||||
p1 = Point3D(0, 0, 0)
|
||||
p2 = Point3D(1, 1, 1)
|
||||
|
||||
l1 = Line3D(p1, p2)
|
||||
l2 = Line3D(Point3D(0, 0, 0), Point3D(3, 4, 0))
|
||||
|
||||
r1 = Ray3D(Point3D(1, 1, 1), Point3D(2, 2, 2))
|
||||
r2 = Ray3D(Point3D(0, 0, 0), Point3D(3, 4, 0))
|
||||
|
||||
s1 = Segment3D(Point3D(0, 0, 0), Point3D(3, 4, 0))
|
||||
|
||||
assert intersection(l1, p1) == [p1]
|
||||
assert intersection(l1, Point3D(x1, 1 + x1, 1)) == []
|
||||
assert intersection(l1, l1.parallel_line(p1)) == [Line3D(Point3D(0, 0, 0), Point3D(1, 1, 1))]
|
||||
assert intersection(l2, r2) == [r2]
|
||||
assert intersection(l2, s1) == [s1]
|
||||
assert intersection(r2, l2) == [r2]
|
||||
assert intersection(r1, Ray3D(Point3D(1, 1, 1), Point3D(-1, -1, -1))) == [Point3D(1, 1, 1)]
|
||||
assert intersection(r1, Segment3D(Point3D(0, 0, 0), Point3D(2, 2, 2))) == [
|
||||
Segment3D(Point3D(1, 1, 1), Point3D(2, 2, 2))]
|
||||
assert intersection(Ray3D(Point3D(1, 0, 0), Point3D(-1, 0, 0)), Ray3D(Point3D(0, 1, 0), Point3D(0, -1, 0))) \
|
||||
== [Point3D(0, 0, 0)]
|
||||
assert intersection(r1, Ray3D(Point3D(2, 2, 2), Point3D(0, 0, 0))) == \
|
||||
[Segment3D(Point3D(1, 1, 1), Point3D(2, 2, 2))]
|
||||
assert intersection(s1, r2) == [s1]
|
||||
|
||||
assert Line3D(Point3D(4, 0, 1), Point3D(0, 4, 1)).intersection(Line3D(Point3D(0, 0, 1), Point3D(4, 4, 1))) == \
|
||||
[Point3D(2, 2, 1)]
|
||||
assert Line3D((0, 1, 2), (0, 2, 3)).intersection(Line3D((0, 1, 2), (0, 1, 1))) == [Point3D(0, 1, 2)]
|
||||
assert Line3D((0, 0), (t, t)).intersection(Line3D((0, 1), (t, t))) == \
|
||||
[Point3D(t, t)]
|
||||
|
||||
assert Ray3D(Point3D(0, 0, 0), Point3D(0, 4, 0)).intersection(Ray3D(Point3D(0, 1, 1), Point3D(0, -1, 1))) == []
|
||||
|
||||
|
||||
def test_is_parallel():
|
||||
p1 = Point3D(0, 0, 0)
|
||||
p2 = Point3D(1, 1, 1)
|
||||
p3 = Point3D(x1, x1, x1)
|
||||
|
||||
l2 = Line(Point(x1, x1), Point(y1, y1))
|
||||
l2_1 = Line(Point(x1, x1), Point(x1, 1 + x1))
|
||||
|
||||
assert Line.is_parallel(Line(Point(0, 0), Point(1, 1)), l2)
|
||||
assert Line.is_parallel(l2, Line(Point(x1, x1), Point(x1, 1 + x1))) is False
|
||||
assert Line.is_parallel(l2, l2.parallel_line(Point(-x1, x1)))
|
||||
assert Line.is_parallel(l2_1, l2_1.parallel_line(Point(0, 0)))
|
||||
assert Line3D(p1, p2).is_parallel(Line3D(p1, p2)) # same as in 2D
|
||||
assert Line3D(Point3D(4, 0, 1), Point3D(0, 4, 1)).is_parallel(Line3D(Point3D(0, 0, 1), Point3D(4, 4, 1))) is False
|
||||
assert Line3D(p1, p2).parallel_line(p3) == Line3D(Point3D(x1, x1, x1),
|
||||
Point3D(x1 + 1, x1 + 1, x1 + 1))
|
||||
assert Line3D(p1, p2).parallel_line(p3.args) == \
|
||||
Line3D(Point3D(x1, x1, x1), Point3D(x1 + 1, x1 + 1, x1 + 1))
|
||||
assert Line3D(Point3D(4, 0, 1), Point3D(0, 4, 1)).is_parallel(Line3D(Point3D(0, 0, 1), Point3D(4, 4, 1))) is False
|
||||
|
||||
|
||||
def test_is_perpendicular():
|
||||
p1 = Point(0, 0)
|
||||
p2 = Point(1, 1)
|
||||
|
||||
l1 = Line(p1, p2)
|
||||
l2 = Line(Point(x1, x1), Point(y1, y1))
|
||||
l1_1 = Line(p1, Point(-x1, x1))
|
||||
# 2D
|
||||
assert Line.is_perpendicular(l1, l1_1)
|
||||
assert Line.is_perpendicular(l1, l2) is False
|
||||
p = l1.random_point()
|
||||
assert l1.perpendicular_segment(p) == p
|
||||
# 3D
|
||||
assert Line3D.is_perpendicular(Line3D(Point3D(0, 0, 0), Point3D(1, 0, 0)),
|
||||
Line3D(Point3D(0, 0, 0), Point3D(0, 1, 0))) is True
|
||||
assert Line3D.is_perpendicular(Line3D(Point3D(0, 0, 0), Point3D(1, 0, 0)),
|
||||
Line3D(Point3D(0, 1, 0), Point3D(1, 1, 0))) is False
|
||||
assert Line3D.is_perpendicular(Line3D(Point3D(0, 0, 0), Point3D(1, 1, 1)),
|
||||
Line3D(Point3D(x1, x1, x1), Point3D(y1, y1, y1))) is False
|
||||
|
||||
|
||||
def test_is_similar():
|
||||
p1 = Point(2000, 2000)
|
||||
p2 = p1.scale(2, 2)
|
||||
|
||||
r1 = Ray3D(Point3D(1, 1, 1), Point3D(1, 0, 0))
|
||||
r2 = Ray(Point(0, 0), Point(0, 1))
|
||||
|
||||
s1 = Segment(Point(0, 0), p1)
|
||||
|
||||
assert s1.is_similar(Segment(p1, p2))
|
||||
assert s1.is_similar(r2) is False
|
||||
assert r1.is_similar(Line3D(Point3D(1, 1, 1), Point3D(1, 0, 0))) is True
|
||||
assert r1.is_similar(Line3D(Point3D(0, 0, 0), Point3D(0, 1, 0))) is False
|
||||
|
||||
|
||||
def test_length():
|
||||
s2 = Segment3D(Point3D(x1, x1, x1), Point3D(y1, y1, y1))
|
||||
assert Line(Point(0, 0), Point(1, 1)).length is oo
|
||||
assert s2.length == sqrt(3) * sqrt((x1 - y1) ** 2)
|
||||
assert Line3D(Point3D(0, 0, 0), Point3D(1, 1, 1)).length is oo
|
||||
|
||||
|
||||
def test_projection():
|
||||
p1 = Point(0, 0)
|
||||
p2 = Point3D(0, 0, 0)
|
||||
p3 = Point(-x1, x1)
|
||||
|
||||
l1 = Line(p1, Point(1, 1))
|
||||
l2 = Line3D(Point3D(0, 0, 0), Point3D(1, 0, 0))
|
||||
l3 = Line3D(p2, Point3D(1, 1, 1))
|
||||
|
||||
r1 = Ray(Point(1, 1), Point(2, 2))
|
||||
|
||||
s1 = Segment(Point2D(0, 0), Point2D(0, 1))
|
||||
s2 = Segment(Point2D(1, 0), Point2D(2, 1/2))
|
||||
|
||||
assert Line(Point(x1, x1), Point(y1, y1)).projection(Point(y1, y1)) == Point(y1, y1)
|
||||
assert Line(Point(x1, x1), Point(x1, 1 + x1)).projection(Point(1, 1)) == Point(x1, 1)
|
||||
assert Segment(Point(-2, 2), Point(0, 4)).projection(r1) == Segment(Point(-1, 3), Point(0, 4))
|
||||
assert Segment(Point(0, 4), Point(-2, 2)).projection(r1) == Segment(Point(0, 4), Point(-1, 3))
|
||||
assert s2.projection(s1) == EmptySet
|
||||
assert l1.projection(p3) == p1
|
||||
assert l1.projection(Ray(p1, Point(-1, 5))) == Ray(Point(0, 0), Point(2, 2))
|
||||
assert l1.projection(Ray(p1, Point(-1, 1))) == p1
|
||||
assert r1.projection(Ray(Point(1, 1), Point(-1, -1))) == Point(1, 1)
|
||||
assert r1.projection(Ray(Point(0, 4), Point(-1, -5))) == Segment(Point(1, 1), Point(2, 2))
|
||||
assert r1.projection(Segment(Point(-1, 5), Point(-5, -10))) == Segment(Point(1, 1), Point(2, 2))
|
||||
assert r1.projection(Ray(Point(1, 1), Point(-1, -1))) == Point(1, 1)
|
||||
assert r1.projection(Ray(Point(0, 4), Point(-1, -5))) == Segment(Point(1, 1), Point(2, 2))
|
||||
assert r1.projection(Segment(Point(-1, 5), Point(-5, -10))) == Segment(Point(1, 1), Point(2, 2))
|
||||
|
||||
assert l3.projection(Ray3D(p2, Point3D(-1, 5, 0))) == Ray3D(Point3D(0, 0, 0), Point3D(Rational(4, 3), Rational(4, 3), Rational(4, 3)))
|
||||
assert l3.projection(Ray3D(p2, Point3D(-1, 1, 1))) == Ray3D(Point3D(0, 0, 0), Point3D(Rational(1, 3), Rational(1, 3), Rational(1, 3)))
|
||||
assert l2.projection(Point3D(5, 5, 0)) == Point3D(5, 0)
|
||||
assert l2.projection(Line3D(Point3D(0, 1, 0), Point3D(1, 1, 0))).equals(l2)
|
||||
|
||||
|
||||
def test_perpendicular_line():
|
||||
# 3d - requires a particular orthogonal to be selected
|
||||
p1, p2, p3 = Point(0, 0, 0), Point(2, 3, 4), Point(-2, 2, 0)
|
||||
l = Line(p1, p2)
|
||||
p = l.perpendicular_line(p3)
|
||||
assert p.p1 == p3
|
||||
assert p.p2 in l
|
||||
# 2d - does not require special selection
|
||||
p1, p2, p3 = Point(0, 0), Point(2, 3), Point(-2, 2)
|
||||
l = Line(p1, p2)
|
||||
p = l.perpendicular_line(p3)
|
||||
assert p.p1 == p3
|
||||
# p is directed from l to p3
|
||||
assert p.direction.unit == (p3 - l.projection(p3)).unit
|
||||
|
||||
|
||||
def test_perpendicular_bisector():
|
||||
s1 = Segment(Point(0, 0), Point(1, 1))
|
||||
aline = Line(Point(S.Half, S.Half), Point(Rational(3, 2), Rational(-1, 2)))
|
||||
on_line = Segment(Point(S.Half, S.Half), Point(Rational(3, 2), Rational(-1, 2))).midpoint
|
||||
|
||||
assert s1.perpendicular_bisector().equals(aline)
|
||||
assert s1.perpendicular_bisector(on_line).equals(Segment(s1.midpoint, on_line))
|
||||
assert s1.perpendicular_bisector(on_line + (1, 0)).equals(aline)
|
||||
|
||||
|
||||
def test_raises():
|
||||
d, e = symbols('a,b', real=True)
|
||||
s = Segment((d, 0), (e, 0))
|
||||
|
||||
raises(TypeError, lambda: Line((1, 1), 1))
|
||||
raises(ValueError, lambda: Line(Point(0, 0), Point(0, 0)))
|
||||
raises(Undecidable, lambda: Point(2 * d, 0) in s)
|
||||
raises(ValueError, lambda: Ray3D(Point(1.0, 1.0)))
|
||||
raises(ValueError, lambda: Line3D(Point3D(0, 0, 0), Point3D(0, 0, 0)))
|
||||
raises(TypeError, lambda: Line3D((1, 1), 1))
|
||||
raises(ValueError, lambda: Line3D(Point3D(0, 0, 0)))
|
||||
raises(TypeError, lambda: Ray((1, 1), 1))
|
||||
raises(GeometryError, lambda: Line(Point(0, 0), Point(1, 0))
|
||||
.projection(Circle(Point(0, 0), 1)))
|
||||
|
||||
|
||||
def test_ray_generation():
|
||||
assert Ray((1, 1), angle=pi / 4) == Ray((1, 1), (2, 2))
|
||||
assert Ray((1, 1), angle=pi / 2) == Ray((1, 1), (1, 2))
|
||||
assert Ray((1, 1), angle=-pi / 2) == Ray((1, 1), (1, 0))
|
||||
assert Ray((1, 1), angle=-3 * pi / 2) == Ray((1, 1), (1, 2))
|
||||
assert Ray((1, 1), angle=5 * pi / 2) == Ray((1, 1), (1, 2))
|
||||
assert Ray((1, 1), angle=5.0 * pi / 2) == Ray((1, 1), (1, 2))
|
||||
assert Ray((1, 1), angle=pi) == Ray((1, 1), (0, 1))
|
||||
assert Ray((1, 1), angle=3.0 * pi) == Ray((1, 1), (0, 1))
|
||||
assert Ray((1, 1), angle=4.0 * pi) == Ray((1, 1), (2, 1))
|
||||
assert Ray((1, 1), angle=0) == Ray((1, 1), (2, 1))
|
||||
assert Ray((1, 1), angle=4.05 * pi) == Ray(Point(1, 1),
|
||||
Point(2, -sqrt(5) * sqrt(2 * sqrt(5) + 10) / 4 - sqrt(
|
||||
2 * sqrt(5) + 10) / 4 + 2 + sqrt(5)))
|
||||
assert Ray((1, 1), angle=4.02 * pi) == Ray(Point(1, 1),
|
||||
Point(2, 1 + tan(4.02 * pi)))
|
||||
assert Ray((1, 1), angle=5) == Ray((1, 1), (2, 1 + tan(5)))
|
||||
|
||||
assert Ray3D((1, 1, 1), direction_ratio=[4, 4, 4]) == Ray3D(Point3D(1, 1, 1), Point3D(5, 5, 5))
|
||||
assert Ray3D((1, 1, 1), direction_ratio=[1, 2, 3]) == Ray3D(Point3D(1, 1, 1), Point3D(2, 3, 4))
|
||||
assert Ray3D((1, 1, 1), direction_ratio=[1, 1, 1]) == Ray3D(Point3D(1, 1, 1), Point3D(2, 2, 2))
|
||||
|
||||
|
||||
def test_issue_7814():
|
||||
circle = Circle(Point(x, 0), y)
|
||||
line = Line(Point(k, z), slope=0)
|
||||
_s = sqrt((y - z)*(y + z))
|
||||
assert line.intersection(circle) == [Point2D(x + _s, z), Point2D(x - _s, z)]
|
||||
|
||||
|
||||
def test_issue_2941():
|
||||
def _check():
|
||||
for f, g in cartes(*[(Line, Ray, Segment)] * 2):
|
||||
l1 = f(a, b)
|
||||
l2 = g(c, d)
|
||||
assert l1.intersection(l2) == l2.intersection(l1)
|
||||
# intersect at end point
|
||||
c, d = (-2, -2), (-2, 0)
|
||||
a, b = (0, 0), (1, 1)
|
||||
_check()
|
||||
# midline intersection
|
||||
c, d = (-2, -3), (-2, 0)
|
||||
_check()
|
||||
|
||||
|
||||
def test_parameter_value():
|
||||
t = Symbol('t')
|
||||
p1, p2 = Point(0, 1), Point(5, 6)
|
||||
l = Line(p1, p2)
|
||||
assert l.parameter_value((5, 6), t) == {t: 1}
|
||||
raises(ValueError, lambda: l.parameter_value((0, 0), t))
|
||||
|
||||
|
||||
def test_bisectors():
|
||||
r1 = Line3D(Point3D(0, 0, 0), Point3D(1, 0, 0))
|
||||
r2 = Line3D(Point3D(0, 0, 0), Point3D(0, 1, 0))
|
||||
bisections = r1.bisectors(r2)
|
||||
assert bisections == [Line3D(Point3D(0, 0, 0), Point3D(1, 1, 0)),
|
||||
Line3D(Point3D(0, 0, 0), Point3D(1, -1, 0))]
|
||||
ans = [Line3D(Point3D(0, 0, 0), Point3D(1, 0, 1)),
|
||||
Line3D(Point3D(0, 0, 0), Point3D(-1, 0, 1))]
|
||||
l1 = (0, 0, 0), (0, 0, 1)
|
||||
l2 = (0, 0), (1, 0)
|
||||
for a, b in cartes((Line, Segment, Ray), repeat=2):
|
||||
assert a(*l1).bisectors(b(*l2)) == ans
|
||||
|
||||
|
||||
def test_issue_8615():
|
||||
a = Line3D(Point3D(6, 5, 0), Point3D(6, -6, 0))
|
||||
b = Line3D(Point3D(6, -1, 19/10), Point3D(6, -1, 0))
|
||||
assert a.intersection(b) == [Point3D(6, -1, 0)]
|
||||
|
||||
|
||||
def test_issue_12598():
|
||||
r1 = Ray(Point(0, 1), Point(0.98, 0.79).n(2))
|
||||
r2 = Ray(Point(0, 0), Point(0.71, 0.71).n(2))
|
||||
assert str(r1.intersection(r2)[0]) == 'Point2D(0.82, 0.82)'
|
||||
l1 = Line((0, 0), (1, 1))
|
||||
l2 = Segment((-1, 1), (0, -1)).n(2)
|
||||
assert str(l1.intersection(l2)[0]) == 'Point2D(-0.33, -0.33)'
|
||||
l2 = Segment((-1, 1), (-1/2, 1/2)).n(2)
|
||||
assert not l1.intersection(l2)
|
||||
@@ -0,0 +1,143 @@
|
||||
from sympy.core.numbers import (Rational, oo)
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import symbols
|
||||
from sympy.functions.elementary.complexes import sign
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.geometry.ellipse import (Circle, Ellipse)
|
||||
from sympy.geometry.line import (Line, Ray2D, Segment2D)
|
||||
from sympy.geometry.parabola import Parabola
|
||||
from sympy.geometry.point import (Point, Point2D)
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
from sympy.abc import x, y
|
||||
|
||||
def test_parabola_geom():
|
||||
a, b = symbols('a b')
|
||||
p1 = Point(0, 0)
|
||||
p2 = Point(3, 7)
|
||||
p3 = Point(0, 4)
|
||||
p4 = Point(6, 0)
|
||||
p5 = Point(a, a)
|
||||
d1 = Line(Point(4, 0), Point(4, 9))
|
||||
d2 = Line(Point(7, 6), Point(3, 6))
|
||||
d3 = Line(Point(4, 0), slope=oo)
|
||||
d4 = Line(Point(7, 6), slope=0)
|
||||
d5 = Line(Point(b, a), slope=oo)
|
||||
d6 = Line(Point(a, b), slope=0)
|
||||
|
||||
half = S.Half
|
||||
|
||||
pa1 = Parabola(None, d2)
|
||||
pa2 = Parabola(directrix=d1)
|
||||
pa3 = Parabola(p1, d1)
|
||||
pa4 = Parabola(p2, d2)
|
||||
pa5 = Parabola(p2, d4)
|
||||
pa6 = Parabola(p3, d2)
|
||||
pa7 = Parabola(p2, d1)
|
||||
pa8 = Parabola(p4, d1)
|
||||
pa9 = Parabola(p4, d3)
|
||||
pa10 = Parabola(p5, d5)
|
||||
pa11 = Parabola(p5, d6)
|
||||
d = Line(Point(3, 7), Point(2, 9))
|
||||
pa12 = Parabola(Point(7, 8), d)
|
||||
pa12r = Parabola(Point(7, 8).reflect(d), d)
|
||||
|
||||
raises(ValueError, lambda:
|
||||
Parabola(Point(7, 8, 9), Line(Point(6, 7), Point(7, 7))))
|
||||
raises(ValueError, lambda:
|
||||
Parabola(Point(0, 2), Line(Point(7, 2), Point(6, 2))))
|
||||
raises(ValueError, lambda: Parabola(Point(7, 8), Point(3, 8)))
|
||||
|
||||
# Basic Stuff
|
||||
assert pa1.focus == Point(0, 0)
|
||||
assert pa1.ambient_dimension == S(2)
|
||||
assert pa2 == pa3
|
||||
assert pa4 != pa7
|
||||
assert pa6 != pa7
|
||||
assert pa6.focus == Point2D(0, 4)
|
||||
assert pa6.focal_length == 1
|
||||
assert pa6.p_parameter == -1
|
||||
assert pa6.vertex == Point2D(0, 5)
|
||||
assert pa6.eccentricity == 1
|
||||
assert pa7.focus == Point2D(3, 7)
|
||||
assert pa7.focal_length == half
|
||||
assert pa7.p_parameter == -half
|
||||
assert pa7.vertex == Point2D(7*half, 7)
|
||||
assert pa4.focal_length == half
|
||||
assert pa4.p_parameter == half
|
||||
assert pa4.vertex == Point2D(3, 13*half)
|
||||
assert pa8.focal_length == 1
|
||||
assert pa8.p_parameter == 1
|
||||
assert pa8.vertex == Point2D(5, 0)
|
||||
assert pa4.focal_length == pa5.focal_length
|
||||
assert pa4.p_parameter == pa5.p_parameter
|
||||
assert pa4.vertex == pa5.vertex
|
||||
assert pa4.equation() == pa5.equation()
|
||||
assert pa8.focal_length == pa9.focal_length
|
||||
assert pa8.p_parameter == pa9.p_parameter
|
||||
assert pa8.vertex == pa9.vertex
|
||||
assert pa8.equation() == pa9.equation()
|
||||
assert pa10.focal_length == pa11.focal_length == sqrt((a - b) ** 2) / 2 # if a, b real == abs(a - b)/2
|
||||
assert pa11.vertex == Point(*pa10.vertex[::-1]) == Point(a,
|
||||
a - sqrt((a - b)**2)*sign(a - b)/2) # change axis x->y, y->x on pa10
|
||||
aos = pa12.axis_of_symmetry
|
||||
assert aos == Line(Point(7, 8), Point(5, 7))
|
||||
assert pa12.directrix == Line(Point(3, 7), Point(2, 9))
|
||||
assert pa12.directrix.angle_between(aos) == S.Pi/2
|
||||
assert pa12.eccentricity == 1
|
||||
assert pa12.equation(x, y) == (x - 7)**2 + (y - 8)**2 - (-2*x - y + 13)**2/5
|
||||
assert pa12.focal_length == 9*sqrt(5)/10
|
||||
assert pa12.focus == Point(7, 8)
|
||||
assert pa12.p_parameter == 9*sqrt(5)/10
|
||||
assert pa12.vertex == Point2D(S(26)/5, S(71)/10)
|
||||
assert pa12r.focal_length == 9*sqrt(5)/10
|
||||
assert pa12r.focus == Point(-S(1)/5, S(22)/5)
|
||||
assert pa12r.p_parameter == -9*sqrt(5)/10
|
||||
assert pa12r.vertex == Point(S(8)/5, S(53)/10)
|
||||
|
||||
|
||||
def test_parabola_intersection():
|
||||
l1 = Line(Point(1, -2), Point(-1,-2))
|
||||
l2 = Line(Point(1, 2), Point(-1,2))
|
||||
l3 = Line(Point(1, 0), Point(-1,0))
|
||||
|
||||
p1 = Point(0,0)
|
||||
p2 = Point(0, -2)
|
||||
p3 = Point(120, -12)
|
||||
parabola1 = Parabola(p1, l1)
|
||||
|
||||
# parabola with parabola
|
||||
assert parabola1.intersection(parabola1) == [parabola1]
|
||||
assert parabola1.intersection(Parabola(p1, l2)) == [Point2D(-2, 0), Point2D(2, 0)]
|
||||
assert parabola1.intersection(Parabola(p2, l3)) == [Point2D(0, -1)]
|
||||
assert parabola1.intersection(Parabola(Point(16, 0), l1)) == [Point2D(8, 15)]
|
||||
assert parabola1.intersection(Parabola(Point(0, 16), l1)) == [Point2D(-6, 8), Point2D(6, 8)]
|
||||
assert parabola1.intersection(Parabola(p3, l3)) == []
|
||||
# parabola with point
|
||||
assert parabola1.intersection(p1) == []
|
||||
assert parabola1.intersection(Point2D(0, -1)) == [Point2D(0, -1)]
|
||||
assert parabola1.intersection(Point2D(4, 3)) == [Point2D(4, 3)]
|
||||
# parabola with line
|
||||
assert parabola1.intersection(Line(Point2D(-7, 3), Point(12, 3))) == [Point2D(-4, 3), Point2D(4, 3)]
|
||||
assert parabola1.intersection(Line(Point(-4, -1), Point(4, -1))) == [Point(0, -1)]
|
||||
assert parabola1.intersection(Line(Point(2, 0), Point(0, -2))) == [Point2D(2, 0)]
|
||||
raises(TypeError, lambda: parabola1.intersection(Line(Point(0, 0, 0), Point(1, 1, 1))))
|
||||
# parabola with segment
|
||||
assert parabola1.intersection(Segment2D((-4, -5), (4, 3))) == [Point2D(0, -1), Point2D(4, 3)]
|
||||
assert parabola1.intersection(Segment2D((0, -5), (0, 6))) == [Point2D(0, -1)]
|
||||
assert parabola1.intersection(Segment2D((-12, -65), (14, -68))) == []
|
||||
# parabola with ray
|
||||
assert parabola1.intersection(Ray2D((-4, -5), (4, 3))) == [Point2D(0, -1), Point2D(4, 3)]
|
||||
assert parabola1.intersection(Ray2D((0, 7), (1, 14))) == [Point2D(14 + 2*sqrt(57), 105 + 14*sqrt(57))]
|
||||
assert parabola1.intersection(Ray2D((0, 7), (0, 14))) == []
|
||||
# parabola with ellipse/circle
|
||||
assert parabola1.intersection(Circle(p1, 2)) == [Point2D(-2, 0), Point2D(2, 0)]
|
||||
assert parabola1.intersection(Circle(p2, 1)) == [Point2D(0, -1)]
|
||||
assert parabola1.intersection(Ellipse(p2, 2, 1)) == [Point2D(0, -1)]
|
||||
assert parabola1.intersection(Ellipse(Point(0, 19), 5, 7)) == []
|
||||
assert parabola1.intersection(Ellipse((0, 3), 12, 4)) == [
|
||||
Point2D(0, -1),
|
||||
Point2D(-4*sqrt(17)/3, Rational(59, 9)),
|
||||
Point2D(4*sqrt(17)/3, Rational(59, 9))]
|
||||
# parabola with unsupported type
|
||||
raises(TypeError, lambda: parabola1.intersection(2))
|
||||
@@ -0,0 +1,268 @@
|
||||
from sympy.core.numbers import (Rational, pi)
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import (Dummy, symbols)
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.functions.elementary.trigonometric import (asin, cos, sin)
|
||||
from sympy.geometry import Line, Point, Ray, Segment, Point3D, Line3D, Ray3D, Segment3D, Plane, Circle
|
||||
from sympy.geometry.util import are_coplanar
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
|
||||
def test_plane():
|
||||
x, y, z, u, v = symbols('x y z u v', real=True)
|
||||
p1 = Point3D(0, 0, 0)
|
||||
p2 = Point3D(1, 1, 1)
|
||||
p3 = Point3D(1, 2, 3)
|
||||
pl3 = Plane(p1, p2, p3)
|
||||
pl4 = Plane(p1, normal_vector=(1, 1, 1))
|
||||
pl4b = Plane(p1, p2)
|
||||
pl5 = Plane(p3, normal_vector=(1, 2, 3))
|
||||
pl6 = Plane(Point3D(2, 3, 7), normal_vector=(2, 2, 2))
|
||||
pl7 = Plane(Point3D(1, -5, -6), normal_vector=(1, -2, 1))
|
||||
pl8 = Plane(p1, normal_vector=(0, 0, 1))
|
||||
pl9 = Plane(p1, normal_vector=(0, 12, 0))
|
||||
pl10 = Plane(p1, normal_vector=(-2, 0, 0))
|
||||
pl11 = Plane(p2, normal_vector=(0, 0, 1))
|
||||
l1 = Line3D(Point3D(5, 0, 0), Point3D(1, -1, 1))
|
||||
l2 = Line3D(Point3D(0, -2, 0), Point3D(3, 1, 1))
|
||||
l3 = Line3D(Point3D(0, -1, 0), Point3D(5, -1, 9))
|
||||
|
||||
raises(ValueError, lambda: Plane(p1, p1, p1))
|
||||
|
||||
assert Plane(p1, p2, p3) != Plane(p1, p3, p2)
|
||||
assert Plane(p1, p2, p3).is_coplanar(Plane(p1, p3, p2))
|
||||
assert Plane(p1, p2, p3).is_coplanar(p1)
|
||||
assert Plane(p1, p2, p3).is_coplanar(Circle(p1, 1)) is False
|
||||
assert Plane(p1, normal_vector=(0, 0, 1)).is_coplanar(Circle(p1, 1))
|
||||
|
||||
assert pl3 == Plane(Point3D(0, 0, 0), normal_vector=(1, -2, 1))
|
||||
assert pl3 != pl4
|
||||
assert pl4 == pl4b
|
||||
assert pl5 == Plane(Point3D(1, 2, 3), normal_vector=(1, 2, 3))
|
||||
|
||||
assert pl5.equation(x, y, z) == x + 2*y + 3*z - 14
|
||||
assert pl3.equation(x, y, z) == x - 2*y + z
|
||||
|
||||
assert pl3.p1 == p1
|
||||
assert pl4.p1 == p1
|
||||
assert pl5.p1 == p3
|
||||
|
||||
assert pl4.normal_vector == (1, 1, 1)
|
||||
assert pl5.normal_vector == (1, 2, 3)
|
||||
|
||||
assert p1 in pl3
|
||||
assert p1 in pl4
|
||||
assert p3 in pl5
|
||||
|
||||
assert pl3.projection(Point(0, 0)) == p1
|
||||
p = pl3.projection(Point3D(1, 1, 0))
|
||||
assert p == Point3D(Rational(7, 6), Rational(2, 3), Rational(1, 6))
|
||||
assert p in pl3
|
||||
|
||||
l = pl3.projection_line(Line(Point(0, 0), Point(1, 1)))
|
||||
assert l == Line3D(Point3D(0, 0, 0), Point3D(Rational(7, 6), Rational(2, 3), Rational(1, 6)))
|
||||
assert l in pl3
|
||||
# get a segment that does not intersect the plane which is also
|
||||
# parallel to pl3's normal veector
|
||||
t = Dummy()
|
||||
r = pl3.random_point()
|
||||
a = pl3.perpendicular_line(r).arbitrary_point(t)
|
||||
s = Segment3D(a.subs(t, 1), a.subs(t, 2))
|
||||
assert s.p1 not in pl3 and s.p2 not in pl3
|
||||
assert pl3.projection_line(s).equals(r)
|
||||
assert pl3.projection_line(Segment(Point(1, 0), Point(1, 1))) == \
|
||||
Segment3D(Point3D(Rational(5, 6), Rational(1, 3), Rational(-1, 6)), Point3D(Rational(7, 6), Rational(2, 3), Rational(1, 6)))
|
||||
assert pl6.projection_line(Ray(Point(1, 0), Point(1, 1))) == \
|
||||
Ray3D(Point3D(Rational(14, 3), Rational(11, 3), Rational(11, 3)), Point3D(Rational(13, 3), Rational(13, 3), Rational(10, 3)))
|
||||
assert pl3.perpendicular_line(r.args) == pl3.perpendicular_line(r)
|
||||
|
||||
assert pl3.is_parallel(pl6) is False
|
||||
assert pl4.is_parallel(pl6)
|
||||
assert pl3.is_parallel(Line(p1, p2))
|
||||
assert pl6.is_parallel(l1) is False
|
||||
|
||||
assert pl3.is_perpendicular(pl6)
|
||||
assert pl4.is_perpendicular(pl7)
|
||||
assert pl6.is_perpendicular(pl7)
|
||||
assert pl6.is_perpendicular(pl4) is False
|
||||
assert pl6.is_perpendicular(l1) is False
|
||||
assert pl6.is_perpendicular(Line((0, 0, 0), (1, 1, 1)))
|
||||
assert pl6.is_perpendicular((1, 1)) is False
|
||||
|
||||
assert pl6.distance(pl6.arbitrary_point(u, v)) == 0
|
||||
assert pl7.distance(pl7.arbitrary_point(u, v)) == 0
|
||||
assert pl6.distance(pl6.arbitrary_point(t)) == 0
|
||||
assert pl7.distance(pl7.arbitrary_point(t)) == 0
|
||||
assert pl6.p1.distance(pl6.arbitrary_point(t)).simplify() == 1
|
||||
assert pl7.p1.distance(pl7.arbitrary_point(t)).simplify() == 1
|
||||
assert pl3.arbitrary_point(t) == Point3D(-sqrt(30)*sin(t)/30 + \
|
||||
2*sqrt(5)*cos(t)/5, sqrt(30)*sin(t)/15 + sqrt(5)*cos(t)/5, sqrt(30)*sin(t)/6)
|
||||
assert pl3.arbitrary_point(u, v) == Point3D(2*u - v, u + 2*v, 5*v)
|
||||
|
||||
assert pl7.distance(Point3D(1, 3, 5)) == 5*sqrt(6)/6
|
||||
assert pl6.distance(Point3D(0, 0, 0)) == 4*sqrt(3)
|
||||
assert pl6.distance(pl6.p1) == 0
|
||||
assert pl7.distance(pl6) == 0
|
||||
assert pl7.distance(l1) == 0
|
||||
assert pl6.distance(Segment3D(Point3D(2, 3, 1), Point3D(1, 3, 4))) == \
|
||||
pl6.distance(Point3D(1, 3, 4)) == 4*sqrt(3)/3
|
||||
assert pl6.distance(Segment3D(Point3D(1, 3, 4), Point3D(0, 3, 7))) == \
|
||||
pl6.distance(Point3D(0, 3, 7)) == 2*sqrt(3)/3
|
||||
assert pl6.distance(Segment3D(Point3D(0, 3, 7), Point3D(-1, 3, 10))) == 0
|
||||
assert pl6.distance(Segment3D(Point3D(-1, 3, 10), Point3D(-2, 3, 13))) == 0
|
||||
assert pl6.distance(Segment3D(Point3D(-2, 3, 13), Point3D(-3, 3, 16))) == \
|
||||
pl6.distance(Point3D(-2, 3, 13)) == 2*sqrt(3)/3
|
||||
assert pl6.distance(Plane(Point3D(5, 5, 5), normal_vector=(8, 8, 8))) == sqrt(3)
|
||||
assert pl6.distance(Ray3D(Point3D(1, 3, 4), direction_ratio=[1, 0, -3])) == 4*sqrt(3)/3
|
||||
assert pl6.distance(Ray3D(Point3D(2, 3, 1), direction_ratio=[-1, 0, 3])) == 0
|
||||
|
||||
|
||||
assert pl6.angle_between(pl3) == pi/2
|
||||
assert pl6.angle_between(pl6) == 0
|
||||
assert pl6.angle_between(pl4) == 0
|
||||
assert pl7.angle_between(Line3D(Point3D(2, 3, 5), Point3D(2, 4, 6))) == \
|
||||
-asin(sqrt(3)/6)
|
||||
assert pl6.angle_between(Ray3D(Point3D(2, 4, 1), Point3D(6, 5, 3))) == \
|
||||
asin(sqrt(7)/3)
|
||||
assert pl7.angle_between(Segment3D(Point3D(5, 6, 1), Point3D(1, 2, 4))) == \
|
||||
asin(7*sqrt(246)/246)
|
||||
|
||||
assert are_coplanar(l1, l2, l3) is False
|
||||
assert are_coplanar(l1) is False
|
||||
assert are_coplanar(Point3D(2, 7, 2), Point3D(0, 0, 2),
|
||||
Point3D(1, 1, 2), Point3D(1, 2, 2))
|
||||
assert are_coplanar(Plane(p1, p2, p3), Plane(p1, p3, p2))
|
||||
assert Plane.are_concurrent(pl3, pl4, pl5) is False
|
||||
assert Plane.are_concurrent(pl6) is False
|
||||
raises(ValueError, lambda: Plane.are_concurrent(Point3D(0, 0, 0)))
|
||||
raises(ValueError, lambda: Plane((1, 2, 3), normal_vector=(0, 0, 0)))
|
||||
|
||||
assert pl3.parallel_plane(Point3D(1, 2, 5)) == Plane(Point3D(1, 2, 5), \
|
||||
normal_vector=(1, -2, 1))
|
||||
|
||||
# perpendicular_plane
|
||||
p = Plane((0, 0, 0), (1, 0, 0))
|
||||
# default
|
||||
assert p.perpendicular_plane() == Plane(Point3D(0, 0, 0), (0, 1, 0))
|
||||
# 1 pt
|
||||
assert p.perpendicular_plane(Point3D(1, 0, 1)) == \
|
||||
Plane(Point3D(1, 0, 1), (0, 1, 0))
|
||||
# pts as tuples
|
||||
assert p.perpendicular_plane((1, 0, 1), (1, 1, 1)) == \
|
||||
Plane(Point3D(1, 0, 1), (0, 0, -1))
|
||||
# more than two planes
|
||||
raises(ValueError, lambda: p.perpendicular_plane((1, 0, 1), (1, 1, 1), (1, 1, 0)))
|
||||
|
||||
a, b = Point3D(0, 0, 0), Point3D(0, 1, 0)
|
||||
Z = (0, 0, 1)
|
||||
p = Plane(a, normal_vector=Z)
|
||||
# case 4
|
||||
assert p.perpendicular_plane(a, b) == Plane(a, (1, 0, 0))
|
||||
n = Point3D(*Z)
|
||||
# case 1
|
||||
assert p.perpendicular_plane(a, n) == Plane(a, (-1, 0, 0))
|
||||
# case 2
|
||||
assert Plane(a, normal_vector=b.args).perpendicular_plane(a, a + b) == \
|
||||
Plane(Point3D(0, 0, 0), (1, 0, 0))
|
||||
# case 1&3
|
||||
assert Plane(b, normal_vector=Z).perpendicular_plane(b, b + n) == \
|
||||
Plane(Point3D(0, 1, 0), (-1, 0, 0))
|
||||
# case 2&3
|
||||
assert Plane(b, normal_vector=b.args).perpendicular_plane(n, n + b) == \
|
||||
Plane(Point3D(0, 0, 1), (1, 0, 0))
|
||||
|
||||
p = Plane(a, normal_vector=(0, 0, 1))
|
||||
assert p.perpendicular_plane() == Plane(a, normal_vector=(1, 0, 0))
|
||||
|
||||
assert pl6.intersection(pl6) == [pl6]
|
||||
assert pl4.intersection(pl4.p1) == [pl4.p1]
|
||||
assert pl3.intersection(pl6) == [
|
||||
Line3D(Point3D(8, 4, 0), Point3D(2, 4, 6))]
|
||||
assert pl3.intersection(Line3D(Point3D(1,2,4), Point3D(4,4,2))) == [
|
||||
Point3D(2, Rational(8, 3), Rational(10, 3))]
|
||||
assert pl3.intersection(Plane(Point3D(6, 0, 0), normal_vector=(2, -5, 3))
|
||||
) == [Line3D(Point3D(-24, -12, 0), Point3D(-25, -13, -1))]
|
||||
assert pl6.intersection(Ray3D(Point3D(2, 3, 1), Point3D(1, 3, 4))) == [
|
||||
Point3D(-1, 3, 10)]
|
||||
assert pl6.intersection(Segment3D(Point3D(2, 3, 1), Point3D(1, 3, 4))) == []
|
||||
assert pl7.intersection(Line(Point(2, 3), Point(4, 2))) == [
|
||||
Point3D(Rational(13, 2), Rational(3, 4), 0)]
|
||||
r = Ray(Point(2, 3), Point(4, 2))
|
||||
assert Plane((1,2,0), normal_vector=(0,0,1)).intersection(r) == [
|
||||
Ray3D(Point(2, 3), Point(4, 2))]
|
||||
assert pl9.intersection(pl8) == [Line3D(Point3D(0, 0, 0), Point3D(12, 0, 0))]
|
||||
assert pl10.intersection(pl11) == [Line3D(Point3D(0, 0, 1), Point3D(0, 2, 1))]
|
||||
assert pl4.intersection(pl8) == [Line3D(Point3D(0, 0, 0), Point3D(1, -1, 0))]
|
||||
assert pl11.intersection(pl8) == []
|
||||
assert pl9.intersection(pl11) == [Line3D(Point3D(0, 0, 1), Point3D(12, 0, 1))]
|
||||
assert pl9.intersection(pl4) == [Line3D(Point3D(0, 0, 0), Point3D(12, 0, -12))]
|
||||
assert pl3.random_point() in pl3
|
||||
assert pl3.random_point(seed=1) in pl3
|
||||
|
||||
# test geometrical entity using equals
|
||||
assert pl4.intersection(pl4.p1)[0].equals(pl4.p1)
|
||||
assert pl3.intersection(pl6)[0].equals(Line3D(Point3D(8, 4, 0), Point3D(2, 4, 6)))
|
||||
pl8 = Plane((1, 2, 0), normal_vector=(0, 0, 1))
|
||||
assert pl8.intersection(Line3D(p1, (1, 12, 0)))[0].equals(Line((0, 0, 0), (0.1, 1.2, 0)))
|
||||
assert pl8.intersection(Ray3D(p1, (1, 12, 0)))[0].equals(Ray((0, 0, 0), (1, 12, 0)))
|
||||
assert pl8.intersection(Segment3D(p1, (21, 1, 0)))[0].equals(Segment3D(p1, (21, 1, 0)))
|
||||
assert pl8.intersection(Plane(p1, normal_vector=(0, 0, 112)))[0].equals(pl8)
|
||||
assert pl8.intersection(Plane(p1, normal_vector=(0, 12, 0)))[0].equals(
|
||||
Line3D(p1, direction_ratio=(112 * pi, 0, 0)))
|
||||
assert pl8.intersection(Plane(p1, normal_vector=(11, 0, 1)))[0].equals(
|
||||
Line3D(p1, direction_ratio=(0, -11, 0)))
|
||||
assert pl8.intersection(Plane(p1, normal_vector=(1, 0, 11)))[0].equals(
|
||||
Line3D(p1, direction_ratio=(0, 11, 0)))
|
||||
assert pl8.intersection(Plane(p1, normal_vector=(-1, -1, -11)))[0].equals(
|
||||
Line3D(p1, direction_ratio=(1, -1, 0)))
|
||||
assert pl3.random_point() in pl3
|
||||
assert len(pl8.intersection(Ray3D(Point3D(0, 2, 3), Point3D(1, 0, 3)))) == 0
|
||||
# check if two plane are equals
|
||||
assert pl6.intersection(pl6)[0].equals(pl6)
|
||||
assert pl8.equals(Plane(p1, normal_vector=(0, 12, 0))) is False
|
||||
assert pl8.equals(pl8)
|
||||
assert pl8.equals(Plane(p1, normal_vector=(0, 0, -12)))
|
||||
assert pl8.equals(Plane(p1, normal_vector=(0, 0, -12*sqrt(3))))
|
||||
assert pl8.equals(p1) is False
|
||||
|
||||
# issue 8570
|
||||
l2 = Line3D(Point3D(Rational(50000004459633, 5000000000000),
|
||||
Rational(-891926590718643, 1000000000000000),
|
||||
Rational(231800966893633, 100000000000000)),
|
||||
Point3D(Rational(50000004459633, 50000000000000),
|
||||
Rational(-222981647679771, 250000000000000),
|
||||
Rational(231800966893633, 100000000000000)))
|
||||
|
||||
p2 = Plane(Point3D(Rational(402775636372767, 100000000000000),
|
||||
Rational(-97224357654973, 100000000000000),
|
||||
Rational(216793600814789, 100000000000000)),
|
||||
(-S('9.00000087501922'), -S('4.81170658872543e-13'),
|
||||
S('0.0')))
|
||||
|
||||
assert str([i.n(2) for i in p2.intersection(l2)]) == \
|
||||
'[Point3D(4.0, -0.89, 2.3)]'
|
||||
|
||||
|
||||
def test_dimension_normalization():
|
||||
A = Plane(Point3D(1, 1, 2), normal_vector=(1, 1, 1))
|
||||
b = Point(1, 1)
|
||||
assert A.projection(b) == Point(Rational(5, 3), Rational(5, 3), Rational(2, 3))
|
||||
|
||||
a, b = Point(0, 0), Point3D(0, 1)
|
||||
Z = (0, 0, 1)
|
||||
p = Plane(a, normal_vector=Z)
|
||||
assert p.perpendicular_plane(a, b) == Plane(Point3D(0, 0, 0), (1, 0, 0))
|
||||
assert Plane((1, 2, 1), (2, 1, 0), (3, 1, 2)
|
||||
).intersection((2, 1)) == [Point(2, 1, 0)]
|
||||
|
||||
|
||||
def test_parameter_value():
|
||||
t, u, v = symbols("t, u v")
|
||||
p1, p2, p3 = Point(0, 0, 0), Point(0, 0, 1), Point(0, 1, 0)
|
||||
p = Plane(p1, p2, p3)
|
||||
assert p.parameter_value((0, -3, 2), t) == {t: asin(2*sqrt(13)/13)}
|
||||
assert p.parameter_value((0, -3, 2), u, v) == {u: 3, v: 2}
|
||||
assert p.parameter_value(p1, t) == p1
|
||||
raises(ValueError, lambda: p.parameter_value((1, 0, 0), t))
|
||||
raises(ValueError, lambda: p.parameter_value(Line(Point(0, 0), Point(1, 1)), t))
|
||||
raises(ValueError, lambda: p.parameter_value((0, -3, 2), t, 1))
|
||||
@@ -0,0 +1,481 @@
|
||||
from sympy.core.basic import Basic
|
||||
from sympy.core.numbers import (I, Rational, pi)
|
||||
from sympy.core.parameters import evaluate
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import Symbol
|
||||
from sympy.core.sympify import sympify
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.geometry import Line, Point, Point2D, Point3D, Line3D, Plane
|
||||
from sympy.geometry.entity import rotate, scale, translate, GeometryEntity
|
||||
from sympy.matrices import Matrix
|
||||
from sympy.utilities.iterables import subsets, permutations, cartes
|
||||
from sympy.utilities.misc import Undecidable
|
||||
from sympy.testing.pytest import raises, warns
|
||||
|
||||
|
||||
def test_point():
|
||||
x = Symbol('x', real=True)
|
||||
y = Symbol('y', real=True)
|
||||
x1 = Symbol('x1', real=True)
|
||||
x2 = Symbol('x2', real=True)
|
||||
y1 = Symbol('y1', real=True)
|
||||
y2 = Symbol('y2', real=True)
|
||||
half = S.Half
|
||||
p1 = Point(x1, x2)
|
||||
p2 = Point(y1, y2)
|
||||
p3 = Point(0, 0)
|
||||
p4 = Point(1, 1)
|
||||
p5 = Point(0, 1)
|
||||
line = Line(Point(1, 0), slope=1)
|
||||
|
||||
assert p1 in p1
|
||||
assert p1 not in p2
|
||||
assert p2.y == y2
|
||||
assert (p3 + p4) == p4
|
||||
assert (p2 - p1) == Point(y1 - x1, y2 - x2)
|
||||
assert -p2 == Point(-y1, -y2)
|
||||
raises(TypeError, lambda: Point(1))
|
||||
raises(ValueError, lambda: Point([1]))
|
||||
raises(ValueError, lambda: Point(3, I))
|
||||
raises(ValueError, lambda: Point(2*I, I))
|
||||
raises(ValueError, lambda: Point(3 + I, I))
|
||||
|
||||
assert Point(34.05, sqrt(3)) == Point(Rational(681, 20), sqrt(3))
|
||||
assert Point.midpoint(p3, p4) == Point(half, half)
|
||||
assert Point.midpoint(p1, p4) == Point(half + half*x1, half + half*x2)
|
||||
assert Point.midpoint(p2, p2) == p2
|
||||
assert p2.midpoint(p2) == p2
|
||||
assert p1.origin == Point(0, 0)
|
||||
|
||||
assert Point.distance(p3, p4) == sqrt(2)
|
||||
assert Point.distance(p1, p1) == 0
|
||||
assert Point.distance(p3, p2) == sqrt(p2.x**2 + p2.y**2)
|
||||
raises(TypeError, lambda: Point.distance(p1, 0))
|
||||
raises(TypeError, lambda: Point.distance(p1, GeometryEntity()))
|
||||
|
||||
# distance should be symmetric
|
||||
assert p1.distance(line) == line.distance(p1)
|
||||
assert p4.distance(line) == line.distance(p4)
|
||||
|
||||
assert Point.taxicab_distance(p4, p3) == 2
|
||||
|
||||
assert Point.canberra_distance(p4, p5) == 1
|
||||
raises(ValueError, lambda: Point.canberra_distance(p3, p3))
|
||||
|
||||
p1_1 = Point(x1, x1)
|
||||
p1_2 = Point(y2, y2)
|
||||
p1_3 = Point(x1 + 1, x1)
|
||||
assert Point.is_collinear(p3)
|
||||
|
||||
with warns(UserWarning, test_stacklevel=False):
|
||||
assert Point.is_collinear(p3, Point(p3, dim=4))
|
||||
assert p3.is_collinear()
|
||||
assert Point.is_collinear(p3, p4)
|
||||
assert Point.is_collinear(p3, p4, p1_1, p1_2)
|
||||
assert Point.is_collinear(p3, p4, p1_1, p1_3) is False
|
||||
assert Point.is_collinear(p3, p3, p4, p5) is False
|
||||
|
||||
raises(TypeError, lambda: Point.is_collinear(line))
|
||||
raises(TypeError, lambda: p1_1.is_collinear(line))
|
||||
|
||||
assert p3.intersection(Point(0, 0)) == [p3]
|
||||
assert p3.intersection(p4) == []
|
||||
assert p3.intersection(line) == []
|
||||
with warns(UserWarning, test_stacklevel=False):
|
||||
assert Point.intersection(Point(0, 0, 0), Point(0, 0)) == [Point(0, 0, 0)]
|
||||
|
||||
x_pos = Symbol('x', positive=True)
|
||||
p2_1 = Point(x_pos, 0)
|
||||
p2_2 = Point(0, x_pos)
|
||||
p2_3 = Point(-x_pos, 0)
|
||||
p2_4 = Point(0, -x_pos)
|
||||
p2_5 = Point(x_pos, 5)
|
||||
assert Point.is_concyclic(p2_1)
|
||||
assert Point.is_concyclic(p2_1, p2_2)
|
||||
assert Point.is_concyclic(p2_1, p2_2, p2_3, p2_4)
|
||||
for pts in permutations((p2_1, p2_2, p2_3, p2_5)):
|
||||
assert Point.is_concyclic(*pts) is False
|
||||
assert Point.is_concyclic(p4, p4 * 2, p4 * 3) is False
|
||||
assert Point(0, 0).is_concyclic((1, 1), (2, 2), (2, 1)) is False
|
||||
assert Point.is_concyclic(Point(0, 0, 0, 0), Point(1, 0, 0, 0), Point(1, 1, 0, 0), Point(1, 1, 1, 0)) is False
|
||||
|
||||
assert p1.is_scalar_multiple(p1)
|
||||
assert p1.is_scalar_multiple(2*p1)
|
||||
assert not p1.is_scalar_multiple(p2)
|
||||
assert Point.is_scalar_multiple(Point(1, 1), (-1, -1))
|
||||
assert Point.is_scalar_multiple(Point(0, 0), (0, -1))
|
||||
# test when is_scalar_multiple can't be determined
|
||||
raises(Undecidable, lambda: Point.is_scalar_multiple(Point(sympify("x1%y1"), sympify("x2%y2")), Point(0, 1)))
|
||||
|
||||
assert Point(0, 1).orthogonal_direction == Point(1, 0)
|
||||
assert Point(1, 0).orthogonal_direction == Point(0, 1)
|
||||
|
||||
assert p1.is_zero is None
|
||||
assert p3.is_zero
|
||||
assert p4.is_zero is False
|
||||
assert p1.is_nonzero is None
|
||||
assert p3.is_nonzero is False
|
||||
assert p4.is_nonzero
|
||||
|
||||
assert p4.scale(2, 3) == Point(2, 3)
|
||||
assert p3.scale(2, 3) == p3
|
||||
|
||||
assert p4.rotate(pi, Point(0.5, 0.5)) == p3
|
||||
assert p1.__radd__(p2) == p1.midpoint(p2).scale(2, 2)
|
||||
assert (-p3).__rsub__(p4) == p3.midpoint(p4).scale(2, 2)
|
||||
|
||||
assert p4 * 5 == Point(5, 5)
|
||||
assert p4 / 5 == Point(0.2, 0.2)
|
||||
assert 5 * p4 == Point(5, 5)
|
||||
|
||||
raises(ValueError, lambda: Point(0, 0) + 10)
|
||||
|
||||
# Point differences should be simplified
|
||||
assert Point(x*(x - 1), y) - Point(x**2 - x, y + 1) == Point(0, -1)
|
||||
|
||||
a, b = S.Half, Rational(1, 3)
|
||||
assert Point(a, b).evalf(2) == \
|
||||
Point(a.n(2), b.n(2), evaluate=False)
|
||||
raises(ValueError, lambda: Point(1, 2) + 1)
|
||||
|
||||
# test project
|
||||
assert Point.project((0, 1), (1, 0)) == Point(0, 0)
|
||||
assert Point.project((1, 1), (1, 0)) == Point(1, 0)
|
||||
raises(ValueError, lambda: Point.project(p1, Point(0, 0)))
|
||||
|
||||
# test transformations
|
||||
p = Point(1, 0)
|
||||
assert p.rotate(pi/2) == Point(0, 1)
|
||||
assert p.rotate(pi/2, p) == p
|
||||
p = Point(1, 1)
|
||||
assert p.scale(2, 3) == Point(2, 3)
|
||||
assert p.translate(1, 2) == Point(2, 3)
|
||||
assert p.translate(1) == Point(2, 1)
|
||||
assert p.translate(y=1) == Point(1, 2)
|
||||
assert p.translate(*p.args) == Point(2, 2)
|
||||
|
||||
# Check invalid input for transform
|
||||
raises(ValueError, lambda: p3.transform(p3))
|
||||
raises(ValueError, lambda: p.transform(Matrix([[1, 0], [0, 1]])))
|
||||
|
||||
# test __contains__
|
||||
assert 0 in Point(0, 0, 0, 0)
|
||||
assert 1 not in Point(0, 0, 0, 0)
|
||||
|
||||
# test affine_rank
|
||||
assert Point.affine_rank() == -1
|
||||
|
||||
|
||||
def test_point3D():
|
||||
x = Symbol('x', real=True)
|
||||
y = Symbol('y', real=True)
|
||||
x1 = Symbol('x1', real=True)
|
||||
x2 = Symbol('x2', real=True)
|
||||
x3 = Symbol('x3', real=True)
|
||||
y1 = Symbol('y1', real=True)
|
||||
y2 = Symbol('y2', real=True)
|
||||
y3 = Symbol('y3', real=True)
|
||||
half = S.Half
|
||||
p1 = Point3D(x1, x2, x3)
|
||||
p2 = Point3D(y1, y2, y3)
|
||||
p3 = Point3D(0, 0, 0)
|
||||
p4 = Point3D(1, 1, 1)
|
||||
p5 = Point3D(0, 1, 2)
|
||||
|
||||
assert p1 in p1
|
||||
assert p1 not in p2
|
||||
assert p2.y == y2
|
||||
assert (p3 + p4) == p4
|
||||
assert (p2 - p1) == Point3D(y1 - x1, y2 - x2, y3 - x3)
|
||||
assert -p2 == Point3D(-y1, -y2, -y3)
|
||||
|
||||
assert Point(34.05, sqrt(3)) == Point(Rational(681, 20), sqrt(3))
|
||||
assert Point3D.midpoint(p3, p4) == Point3D(half, half, half)
|
||||
assert Point3D.midpoint(p1, p4) == Point3D(half + half*x1, half + half*x2,
|
||||
half + half*x3)
|
||||
assert Point3D.midpoint(p2, p2) == p2
|
||||
assert p2.midpoint(p2) == p2
|
||||
|
||||
assert Point3D.distance(p3, p4) == sqrt(3)
|
||||
assert Point3D.distance(p1, p1) == 0
|
||||
assert Point3D.distance(p3, p2) == sqrt(p2.x**2 + p2.y**2 + p2.z**2)
|
||||
|
||||
p1_1 = Point3D(x1, x1, x1)
|
||||
p1_2 = Point3D(y2, y2, y2)
|
||||
p1_3 = Point3D(x1 + 1, x1, x1)
|
||||
Point3D.are_collinear(p3)
|
||||
assert Point3D.are_collinear(p3, p4)
|
||||
assert Point3D.are_collinear(p3, p4, p1_1, p1_2)
|
||||
assert Point3D.are_collinear(p3, p4, p1_1, p1_3) is False
|
||||
assert Point3D.are_collinear(p3, p3, p4, p5) is False
|
||||
|
||||
assert p3.intersection(Point3D(0, 0, 0)) == [p3]
|
||||
assert p3.intersection(p4) == []
|
||||
|
||||
|
||||
assert p4 * 5 == Point3D(5, 5, 5)
|
||||
assert p4 / 5 == Point3D(0.2, 0.2, 0.2)
|
||||
assert 5 * p4 == Point3D(5, 5, 5)
|
||||
|
||||
raises(ValueError, lambda: Point3D(0, 0, 0) + 10)
|
||||
|
||||
# Test coordinate properties
|
||||
assert p1.coordinates == (x1, x2, x3)
|
||||
assert p2.coordinates == (y1, y2, y3)
|
||||
assert p3.coordinates == (0, 0, 0)
|
||||
assert p4.coordinates == (1, 1, 1)
|
||||
assert p5.coordinates == (0, 1, 2)
|
||||
assert p5.x == 0
|
||||
assert p5.y == 1
|
||||
assert p5.z == 2
|
||||
|
||||
# Point differences should be simplified
|
||||
assert Point3D(x*(x - 1), y, 2) - Point3D(x**2 - x, y + 1, 1) == \
|
||||
Point3D(0, -1, 1)
|
||||
|
||||
a, b, c = S.Half, Rational(1, 3), Rational(1, 4)
|
||||
assert Point3D(a, b, c).evalf(2) == \
|
||||
Point(a.n(2), b.n(2), c.n(2), evaluate=False)
|
||||
raises(ValueError, lambda: Point3D(1, 2, 3) + 1)
|
||||
|
||||
# test transformations
|
||||
p = Point3D(1, 1, 1)
|
||||
assert p.scale(2, 3) == Point3D(2, 3, 1)
|
||||
assert p.translate(1, 2) == Point3D(2, 3, 1)
|
||||
assert p.translate(1) == Point3D(2, 1, 1)
|
||||
assert p.translate(z=1) == Point3D(1, 1, 2)
|
||||
assert p.translate(*p.args) == Point3D(2, 2, 2)
|
||||
|
||||
# Test __new__
|
||||
assert Point3D(0.1, 0.2, evaluate=False, on_morph='ignore').args[0].is_Float
|
||||
|
||||
# Test length property returns correctly
|
||||
assert p.length == 0
|
||||
assert p1_1.length == 0
|
||||
assert p1_2.length == 0
|
||||
|
||||
# Test are_colinear type error
|
||||
raises(TypeError, lambda: Point3D.are_collinear(p, x))
|
||||
|
||||
# Test are_coplanar
|
||||
assert Point.are_coplanar()
|
||||
assert Point.are_coplanar((1, 2, 0), (1, 2, 0), (1, 3, 0))
|
||||
assert Point.are_coplanar((1, 2, 0), (1, 2, 3))
|
||||
with warns(UserWarning, test_stacklevel=False):
|
||||
raises(ValueError, lambda: Point2D.are_coplanar((1, 2), (1, 2, 3)))
|
||||
assert Point3D.are_coplanar((1, 2, 0), (1, 2, 3))
|
||||
assert Point.are_coplanar((0, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 1)) is False
|
||||
planar2 = Point3D(1, -1, 1)
|
||||
planar3 = Point3D(-1, 1, 1)
|
||||
assert Point3D.are_coplanar(p, planar2, planar3) == True
|
||||
assert Point3D.are_coplanar(p, planar2, planar3, p3) == False
|
||||
assert Point.are_coplanar(p, planar2)
|
||||
planar2 = Point3D(1, 1, 2)
|
||||
planar3 = Point3D(1, 1, 3)
|
||||
assert Point3D.are_coplanar(p, planar2, planar3) # line, not plane
|
||||
plane = Plane((1, 2, 1), (2, 1, 0), (3, 1, 2))
|
||||
assert Point.are_coplanar(*[plane.projection(((-1)**i, i)) for i in range(4)])
|
||||
|
||||
# all 2D points are coplanar
|
||||
assert Point.are_coplanar(Point(x, y), Point(x, x + y), Point(y, x + 2)) is True
|
||||
|
||||
# Test Intersection
|
||||
assert planar2.intersection(Line3D(p, planar3)) == [Point3D(1, 1, 2)]
|
||||
|
||||
# Test Scale
|
||||
assert planar2.scale(1, 1, 1) == planar2
|
||||
assert planar2.scale(2, 2, 2, planar3) == Point3D(1, 1, 1)
|
||||
assert planar2.scale(1, 1, 1, p3) == planar2
|
||||
|
||||
# Test Transform
|
||||
identity = Matrix([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]])
|
||||
assert p.transform(identity) == p
|
||||
trans = Matrix([[1, 0, 0, 1], [0, 1, 0, 1], [0, 0, 1, 1], [0, 0, 0, 1]])
|
||||
assert p.transform(trans) == Point3D(2, 2, 2)
|
||||
raises(ValueError, lambda: p.transform(p))
|
||||
raises(ValueError, lambda: p.transform(Matrix([[1, 0], [0, 1]])))
|
||||
|
||||
# Test Equals
|
||||
assert p.equals(x1) == False
|
||||
|
||||
# Test __sub__
|
||||
p_4d = Point(0, 0, 0, 1)
|
||||
with warns(UserWarning, test_stacklevel=False):
|
||||
assert p - p_4d == Point(1, 1, 1, -1)
|
||||
p_4d3d = Point(0, 0, 1, 0)
|
||||
with warns(UserWarning, test_stacklevel=False):
|
||||
assert p - p_4d3d == Point(1, 1, 0, 0)
|
||||
|
||||
|
||||
def test_Point2D():
|
||||
|
||||
# Test Distance
|
||||
p1 = Point2D(1, 5)
|
||||
p2 = Point2D(4, 2.5)
|
||||
p3 = (6, 3)
|
||||
assert p1.distance(p2) == sqrt(61)/2
|
||||
assert p2.distance(p3) == sqrt(17)/2
|
||||
|
||||
# Test coordinates
|
||||
assert p1.x == 1
|
||||
assert p1.y == 5
|
||||
assert p2.x == 4
|
||||
assert p2.y == S(5)/2
|
||||
assert p1.coordinates == (1, 5)
|
||||
assert p2.coordinates == (4, S(5)/2)
|
||||
|
||||
# test bounds
|
||||
assert p1.bounds == (1, 5, 1, 5)
|
||||
|
||||
def test_issue_9214():
|
||||
p1 = Point3D(4, -2, 6)
|
||||
p2 = Point3D(1, 2, 3)
|
||||
p3 = Point3D(7, 2, 3)
|
||||
|
||||
assert Point3D.are_collinear(p1, p2, p3) is False
|
||||
|
||||
|
||||
def test_issue_11617():
|
||||
p1 = Point3D(1,0,2)
|
||||
p2 = Point2D(2,0)
|
||||
|
||||
with warns(UserWarning, test_stacklevel=False):
|
||||
assert p1.distance(p2) == sqrt(5)
|
||||
|
||||
|
||||
def test_transform():
|
||||
p = Point(1, 1)
|
||||
assert p.transform(rotate(pi/2)) == Point(-1, 1)
|
||||
assert p.transform(scale(3, 2)) == Point(3, 2)
|
||||
assert p.transform(translate(1, 2)) == Point(2, 3)
|
||||
assert Point(1, 1).scale(2, 3, (4, 5)) == \
|
||||
Point(-2, -7)
|
||||
assert Point(1, 1).translate(4, 5) == \
|
||||
Point(5, 6)
|
||||
|
||||
|
||||
def test_concyclic_doctest_bug():
|
||||
p1, p2 = Point(-1, 0), Point(1, 0)
|
||||
p3, p4 = Point(0, 1), Point(-1, 2)
|
||||
assert Point.is_concyclic(p1, p2, p3)
|
||||
assert not Point.is_concyclic(p1, p2, p3, p4)
|
||||
|
||||
|
||||
def test_arguments():
|
||||
"""Functions accepting `Point` objects in `geometry`
|
||||
should also accept tuples and lists and
|
||||
automatically convert them to points."""
|
||||
|
||||
singles2d = ((1,2), [1,2], Point(1,2))
|
||||
singles2d2 = ((1,3), [1,3], Point(1,3))
|
||||
doubles2d = cartes(singles2d, singles2d2)
|
||||
p2d = Point2D(1,2)
|
||||
singles3d = ((1,2,3), [1,2,3], Point(1,2,3))
|
||||
doubles3d = subsets(singles3d, 2)
|
||||
p3d = Point3D(1,2,3)
|
||||
singles4d = ((1,2,3,4), [1,2,3,4], Point(1,2,3,4))
|
||||
doubles4d = subsets(singles4d, 2)
|
||||
p4d = Point(1,2,3,4)
|
||||
|
||||
# test 2D
|
||||
test_single = ['distance', 'is_scalar_multiple', 'taxicab_distance', 'midpoint', 'intersection', 'dot', 'equals', '__add__', '__sub__']
|
||||
test_double = ['is_concyclic', 'is_collinear']
|
||||
for p in singles2d:
|
||||
Point2D(p)
|
||||
for func in test_single:
|
||||
for p in singles2d:
|
||||
getattr(p2d, func)(p)
|
||||
for func in test_double:
|
||||
for p in doubles2d:
|
||||
getattr(p2d, func)(*p)
|
||||
|
||||
# test 3D
|
||||
test_double = ['is_collinear']
|
||||
for p in singles3d:
|
||||
Point3D(p)
|
||||
for func in test_single:
|
||||
for p in singles3d:
|
||||
getattr(p3d, func)(p)
|
||||
for func in test_double:
|
||||
for p in doubles3d:
|
||||
getattr(p3d, func)(*p)
|
||||
|
||||
# test 4D
|
||||
test_double = ['is_collinear']
|
||||
for p in singles4d:
|
||||
Point(p)
|
||||
for func in test_single:
|
||||
for p in singles4d:
|
||||
getattr(p4d, func)(p)
|
||||
for func in test_double:
|
||||
for p in doubles4d:
|
||||
getattr(p4d, func)(*p)
|
||||
|
||||
# test evaluate=False for ops
|
||||
x = Symbol('x')
|
||||
a = Point(0, 1)
|
||||
assert a + (0.1, x) == Point(0.1, 1 + x, evaluate=False)
|
||||
a = Point(0, 1)
|
||||
assert a/10.0 == Point(0, 0.1, evaluate=False)
|
||||
a = Point(0, 1)
|
||||
assert a*10.0 == Point(0, 10.0, evaluate=False)
|
||||
|
||||
# test evaluate=False when changing dimensions
|
||||
u = Point(.1, .2, evaluate=False)
|
||||
u4 = Point(u, dim=4, on_morph='ignore')
|
||||
assert u4.args == (.1, .2, 0, 0)
|
||||
assert all(i.is_Float for i in u4.args[:2])
|
||||
# and even when *not* changing dimensions
|
||||
assert all(i.is_Float for i in Point(u).args)
|
||||
|
||||
# never raise error if creating an origin
|
||||
assert Point(dim=3, on_morph='error')
|
||||
|
||||
# raise error with unmatched dimension
|
||||
raises(ValueError, lambda: Point(1, 1, dim=3, on_morph='error'))
|
||||
# test unknown on_morph
|
||||
raises(ValueError, lambda: Point(1, 1, dim=3, on_morph='unknown'))
|
||||
# test invalid expressions
|
||||
raises(TypeError, lambda: Point(Basic(), Basic()))
|
||||
|
||||
def test_unit():
|
||||
assert Point(1, 1).unit == Point(sqrt(2)/2, sqrt(2)/2)
|
||||
|
||||
|
||||
def test_dot():
|
||||
raises(TypeError, lambda: Point(1, 2).dot(Line((0, 0), (1, 1))))
|
||||
|
||||
|
||||
def test__normalize_dimension():
|
||||
assert Point._normalize_dimension(Point(1, 2), Point(3, 4)) == [
|
||||
Point(1, 2), Point(3, 4)]
|
||||
assert Point._normalize_dimension(
|
||||
Point(1, 2), Point(3, 4, 0), on_morph='ignore') == [
|
||||
Point(1, 2, 0), Point(3, 4, 0)]
|
||||
|
||||
|
||||
def test_issue_22684():
|
||||
# Used to give an error
|
||||
with evaluate(False):
|
||||
Point(1, 2)
|
||||
|
||||
|
||||
def test_direction_cosine():
|
||||
p1 = Point3D(0, 0, 0)
|
||||
p2 = Point3D(1, 1, 1)
|
||||
|
||||
assert p1.direction_cosine(Point3D(1, 0, 0)) == [1, 0, 0]
|
||||
assert p1.direction_cosine(Point3D(0, 1, 0)) == [0, 1, 0]
|
||||
assert p1.direction_cosine(Point3D(0, 0, pi)) == [0, 0, 1]
|
||||
|
||||
assert p1.direction_cosine(Point3D(5, 0, 0)) == [1, 0, 0]
|
||||
assert p1.direction_cosine(Point3D(0, sqrt(3), 0)) == [0, 1, 0]
|
||||
assert p1.direction_cosine(Point3D(0, 0, 5)) == [0, 0, 1]
|
||||
|
||||
assert p1.direction_cosine(Point3D(2.4, 2.4, 0)) == [sqrt(2)/2, sqrt(2)/2, 0]
|
||||
assert p1.direction_cosine(Point3D(1, 1, 1)) == [sqrt(3) / 3, sqrt(3) / 3, sqrt(3) / 3]
|
||||
assert p1.direction_cosine(Point3D(-12, 0 -15)) == [-4*sqrt(41)/41, -5*sqrt(41)/41, 0]
|
||||
|
||||
assert p2.direction_cosine(Point3D(0, 0, 0)) == [-sqrt(3) / 3, -sqrt(3) / 3, -sqrt(3) / 3]
|
||||
assert p2.direction_cosine(Point3D(1, 1, 12)) == [0, 0, 1]
|
||||
assert p2.direction_cosine(Point3D(12, 1, 12)) == [sqrt(2) / 2, 0, sqrt(2) / 2]
|
||||
@@ -0,0 +1,676 @@
|
||||
from sympy.core.numbers import (Float, Rational, oo, pi)
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import (Symbol, symbols)
|
||||
from sympy.functions.elementary.complexes import Abs
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.functions.elementary.trigonometric import (acos, cos, sin)
|
||||
from sympy.functions.elementary.trigonometric import tan
|
||||
from sympy.geometry import (Circle, Ellipse, GeometryError, Point, Point2D,
|
||||
Polygon, Ray, RegularPolygon, Segment, Triangle,
|
||||
are_similar, convex_hull, intersection, Line, Ray2D)
|
||||
from sympy.testing.pytest import raises, slow, warns
|
||||
from sympy.core.random import verify_numerically
|
||||
from sympy.geometry.polygon import rad, deg
|
||||
from sympy.integrals.integrals import integrate
|
||||
from sympy.utilities.iterables import rotate_left
|
||||
|
||||
|
||||
def feq(a, b):
|
||||
"""Test if two floating point values are 'equal'."""
|
||||
t_float = Float("1.0E-10")
|
||||
return -t_float < a - b < t_float
|
||||
|
||||
@slow
|
||||
def test_polygon():
|
||||
x = Symbol('x', real=True)
|
||||
y = Symbol('y', real=True)
|
||||
q = Symbol('q', real=True)
|
||||
u = Symbol('u', real=True)
|
||||
v = Symbol('v', real=True)
|
||||
w = Symbol('w', real=True)
|
||||
x1 = Symbol('x1', real=True)
|
||||
half = S.Half
|
||||
a, b, c = Point(0, 0), Point(2, 0), Point(3, 3)
|
||||
t = Triangle(a, b, c)
|
||||
assert Polygon(Point(0, 0)) == Point(0, 0)
|
||||
assert Polygon(a, Point(1, 0), b, c) == t
|
||||
assert Polygon(Point(1, 0), b, c, a) == t
|
||||
assert Polygon(b, c, a, Point(1, 0)) == t
|
||||
# 2 "remove folded" tests
|
||||
assert Polygon(a, Point(3, 0), b, c) == t
|
||||
assert Polygon(a, b, Point(3, -1), b, c) == t
|
||||
# remove multiple collinear points
|
||||
assert Polygon(Point(-4, 15), Point(-11, 15), Point(-15, 15),
|
||||
Point(-15, 33/5), Point(-15, -87/10), Point(-15, -15),
|
||||
Point(-42/5, -15), Point(-2, -15), Point(7, -15), Point(15, -15),
|
||||
Point(15, -3), Point(15, 10), Point(15, 15)) == \
|
||||
Polygon(Point(-15, -15), Point(15, -15), Point(15, 15), Point(-15, 15))
|
||||
|
||||
p1 = Polygon(
|
||||
Point(0, 0), Point(3, -1),
|
||||
Point(6, 0), Point(4, 5),
|
||||
Point(2, 3), Point(0, 3))
|
||||
p2 = Polygon(
|
||||
Point(6, 0), Point(3, -1),
|
||||
Point(0, 0), Point(0, 3),
|
||||
Point(2, 3), Point(4, 5))
|
||||
p3 = Polygon(
|
||||
Point(0, 0), Point(3, 0),
|
||||
Point(5, 2), Point(4, 4))
|
||||
p4 = Polygon(
|
||||
Point(0, 0), Point(4, 4),
|
||||
Point(5, 2), Point(3, 0))
|
||||
p5 = Polygon(
|
||||
Point(0, 0), Point(4, 4),
|
||||
Point(0, 4))
|
||||
p6 = Polygon(
|
||||
Point(-11, 1), Point(-9, 6.6),
|
||||
Point(-4, -3), Point(-8.4, -8.7))
|
||||
p7 = Polygon(
|
||||
Point(x, y), Point(q, u),
|
||||
Point(v, w))
|
||||
p8 = Polygon(
|
||||
Point(x, y), Point(v, w),
|
||||
Point(q, u))
|
||||
p9 = Polygon(
|
||||
Point(0, 0), Point(4, 4),
|
||||
Point(3, 0), Point(5, 2))
|
||||
p10 = Polygon(
|
||||
Point(0, 2), Point(2, 2),
|
||||
Point(0, 0), Point(2, 0))
|
||||
p11 = Polygon(Point(0, 0), 1, n=3)
|
||||
p12 = Polygon(Point(0, 0), 1, 0, n=3)
|
||||
p13 = Polygon(
|
||||
Point(0, 0),Point(8, 8),
|
||||
Point(23, 20),Point(0, 20))
|
||||
p14 = Polygon(*rotate_left(p13.args, 1))
|
||||
|
||||
|
||||
r = Ray(Point(-9, 6.6), Point(-9, 5.5))
|
||||
#
|
||||
# General polygon
|
||||
#
|
||||
assert p1 == p2
|
||||
assert len(p1.args) == 6
|
||||
assert len(p1.sides) == 6
|
||||
assert p1.perimeter == 5 + 2*sqrt(10) + sqrt(29) + sqrt(8)
|
||||
assert p1.area == 22
|
||||
assert not p1.is_convex()
|
||||
assert Polygon((-1, 1), (2, -1), (2, 1), (-1, -1), (3, 0)
|
||||
).is_convex() is False
|
||||
# ensure convex for both CW and CCW point specification
|
||||
assert p3.is_convex()
|
||||
assert p4.is_convex()
|
||||
dict5 = p5.angles
|
||||
assert dict5[Point(0, 0)] == pi / 4
|
||||
assert dict5[Point(0, 4)] == pi / 2
|
||||
assert p5.encloses_point(Point(x, y)) is None
|
||||
assert p5.encloses_point(Point(1, 3))
|
||||
assert p5.encloses_point(Point(0, 0)) is False
|
||||
assert p5.encloses_point(Point(4, 0)) is False
|
||||
assert p1.encloses(Circle(Point(2.5, 2.5), 5)) is False
|
||||
assert p1.encloses(Ellipse(Point(2.5, 2), 5, 6)) is False
|
||||
assert p5.plot_interval('x') == [x, 0, 1]
|
||||
assert p5.distance(
|
||||
Polygon(Point(10, 10), Point(14, 14), Point(10, 14))) == 6 * sqrt(2)
|
||||
assert p5.distance(
|
||||
Polygon(Point(1, 8), Point(5, 8), Point(8, 12), Point(1, 12))) == 4
|
||||
with warns(UserWarning, \
|
||||
match="Polygons may intersect producing erroneous output"):
|
||||
Polygon(Point(0, 0), Point(1, 0), Point(1, 1)).distance(
|
||||
Polygon(Point(0, 0), Point(0, 1), Point(1, 1)))
|
||||
assert hash(p5) == hash(Polygon(Point(0, 0), Point(4, 4), Point(0, 4)))
|
||||
assert hash(p1) == hash(p2)
|
||||
assert hash(p7) == hash(p8)
|
||||
assert hash(p3) != hash(p9)
|
||||
assert p5 == Polygon(Point(4, 4), Point(0, 4), Point(0, 0))
|
||||
assert Polygon(Point(4, 4), Point(0, 4), Point(0, 0)) in p5
|
||||
assert p5 != Point(0, 4)
|
||||
assert Point(0, 1) in p5
|
||||
assert p5.arbitrary_point('t').subs(Symbol('t', real=True), 0) == \
|
||||
Point(0, 0)
|
||||
raises(ValueError, lambda: Polygon(
|
||||
Point(x, 0), Point(0, y), Point(x, y)).arbitrary_point('x'))
|
||||
assert p6.intersection(r) == [Point(-9, Rational(-84, 13)), Point(-9, Rational(33, 5))]
|
||||
assert p10.area == 0
|
||||
assert p11 == RegularPolygon(Point(0, 0), 1, 3, 0)
|
||||
assert p11 == p12
|
||||
assert p11.vertices[0] == Point(1, 0)
|
||||
assert p11.args[0] == Point(0, 0)
|
||||
p11.spin(pi/2)
|
||||
assert p11.vertices[0] == Point(0, 1)
|
||||
#
|
||||
# Regular polygon
|
||||
#
|
||||
p1 = RegularPolygon(Point(0, 0), 10, 5)
|
||||
p2 = RegularPolygon(Point(0, 0), 5, 5)
|
||||
raises(GeometryError, lambda: RegularPolygon(Point(0, 0), Point(0,
|
||||
1), Point(1, 1)))
|
||||
raises(GeometryError, lambda: RegularPolygon(Point(0, 0), 1, 2))
|
||||
raises(ValueError, lambda: RegularPolygon(Point(0, 0), 1, 2.5))
|
||||
|
||||
assert p1 != p2
|
||||
assert p1.interior_angle == pi*Rational(3, 5)
|
||||
assert p1.exterior_angle == pi*Rational(2, 5)
|
||||
assert p2.apothem == 5*cos(pi/5)
|
||||
assert p2.circumcenter == p1.circumcenter == Point(0, 0)
|
||||
assert p1.circumradius == p1.radius == 10
|
||||
assert p2.circumcircle == Circle(Point(0, 0), 5)
|
||||
assert p2.incircle == Circle(Point(0, 0), p2.apothem)
|
||||
assert p2.inradius == p2.apothem == (5 * (1 + sqrt(5)) / 4)
|
||||
p2.spin(pi / 10)
|
||||
dict1 = p2.angles
|
||||
assert dict1[Point(0, 5)] == 3 * pi / 5
|
||||
assert p1.is_convex()
|
||||
assert p1.rotation == 0
|
||||
assert p1.encloses_point(Point(0, 0))
|
||||
assert p1.encloses_point(Point(11, 0)) is False
|
||||
assert p2.encloses_point(Point(0, 4.9))
|
||||
p1.spin(pi/3)
|
||||
assert p1.rotation == pi/3
|
||||
assert p1.vertices[0] == Point(5, 5*sqrt(3))
|
||||
for var in p1.args:
|
||||
if isinstance(var, Point):
|
||||
assert var == Point(0, 0)
|
||||
else:
|
||||
assert var in (5, 10, pi / 3)
|
||||
assert p1 != Point(0, 0)
|
||||
assert p1 != p5
|
||||
|
||||
# while spin works in place (notice that rotation is 2pi/3 below)
|
||||
# rotate returns a new object
|
||||
p1_old = p1
|
||||
assert p1.rotate(pi/3) == RegularPolygon(Point(0, 0), 10, 5, pi*Rational(2, 3))
|
||||
assert p1 == p1_old
|
||||
|
||||
assert p1.area == (-250*sqrt(5) + 1250)/(4*tan(pi/5))
|
||||
assert p1.length == 20*sqrt(-sqrt(5)/8 + Rational(5, 8))
|
||||
assert p1.scale(2, 2) == \
|
||||
RegularPolygon(p1.center, p1.radius*2, p1._n, p1.rotation)
|
||||
assert RegularPolygon((0, 0), 1, 4).scale(2, 3) == \
|
||||
Polygon(Point(2, 0), Point(0, 3), Point(-2, 0), Point(0, -3))
|
||||
|
||||
assert repr(p1) == str(p1)
|
||||
|
||||
#
|
||||
# Angles
|
||||
#
|
||||
angles = p4.angles
|
||||
assert feq(angles[Point(0, 0)].evalf(), Float("0.7853981633974483"))
|
||||
assert feq(angles[Point(4, 4)].evalf(), Float("1.2490457723982544"))
|
||||
assert feq(angles[Point(5, 2)].evalf(), Float("1.8925468811915388"))
|
||||
assert feq(angles[Point(3, 0)].evalf(), Float("2.3561944901923449"))
|
||||
|
||||
angles = p3.angles
|
||||
assert feq(angles[Point(0, 0)].evalf(), Float("0.7853981633974483"))
|
||||
assert feq(angles[Point(4, 4)].evalf(), Float("1.2490457723982544"))
|
||||
assert feq(angles[Point(5, 2)].evalf(), Float("1.8925468811915388"))
|
||||
assert feq(angles[Point(3, 0)].evalf(), Float("2.3561944901923449"))
|
||||
|
||||
# https://github.com/sympy/sympy/issues/24885
|
||||
interior_angles_sum = sum(p13.angles.values())
|
||||
assert feq(interior_angles_sum, (len(p13.angles) - 2)*pi )
|
||||
interior_angles_sum = sum(p14.angles.values())
|
||||
assert feq(interior_angles_sum, (len(p14.angles) - 2)*pi )
|
||||
|
||||
#
|
||||
# Triangle
|
||||
#
|
||||
p1 = Point(0, 0)
|
||||
p2 = Point(5, 0)
|
||||
p3 = Point(0, 5)
|
||||
t1 = Triangle(p1, p2, p3)
|
||||
t2 = Triangle(p1, p2, Point(Rational(5, 2), sqrt(Rational(75, 4))))
|
||||
t3 = Triangle(p1, Point(x1, 0), Point(0, x1))
|
||||
s1 = t1.sides
|
||||
assert Triangle(p1, p2, p1) == Polygon(p1, p2, p1) == Segment(p1, p2)
|
||||
raises(GeometryError, lambda: Triangle(Point(0, 0)))
|
||||
|
||||
# Basic stuff
|
||||
assert Triangle(p1, p1, p1) == p1
|
||||
assert Triangle(p2, p2*2, p2*3) == Segment(p2, p2*3)
|
||||
assert t1.area == Rational(25, 2)
|
||||
assert t1.is_right()
|
||||
assert t2.is_right() is False
|
||||
assert t3.is_right()
|
||||
assert p1 in t1
|
||||
assert t1.sides[0] in t1
|
||||
assert Segment((0, 0), (1, 0)) in t1
|
||||
assert Point(5, 5) not in t2
|
||||
assert t1.is_convex()
|
||||
assert feq(t1.angles[p1].evalf(), pi.evalf()/2)
|
||||
|
||||
assert t1.is_equilateral() is False
|
||||
assert t2.is_equilateral()
|
||||
assert t3.is_equilateral() is False
|
||||
assert are_similar(t1, t2) is False
|
||||
assert are_similar(t1, t3)
|
||||
assert are_similar(t2, t3) is False
|
||||
assert t1.is_similar(Point(0, 0)) is False
|
||||
assert t1.is_similar(t2) is False
|
||||
|
||||
# Bisectors
|
||||
bisectors = t1.bisectors()
|
||||
assert bisectors[p1] == Segment(
|
||||
p1, Point(Rational(5, 2), Rational(5, 2)))
|
||||
assert t2.bisectors()[p2] == Segment(
|
||||
Point(5, 0), Point(Rational(5, 4), 5*sqrt(3)/4))
|
||||
p4 = Point(0, x1)
|
||||
assert t3.bisectors()[p4] == Segment(p4, Point(x1*(sqrt(2) - 1), 0))
|
||||
ic = (250 - 125*sqrt(2))/50
|
||||
assert t1.incenter == Point(ic, ic)
|
||||
|
||||
# Inradius
|
||||
assert t1.inradius == t1.incircle.radius == 5 - 5*sqrt(2)/2
|
||||
assert t2.inradius == t2.incircle.radius == 5*sqrt(3)/6
|
||||
assert t3.inradius == t3.incircle.radius == x1**2/((2 + sqrt(2))*Abs(x1))
|
||||
|
||||
# Exradius
|
||||
assert t1.exradii[t1.sides[2]] == 5*sqrt(2)/2
|
||||
|
||||
# Excenters
|
||||
assert t1.excenters[t1.sides[2]] == Point2D(25*sqrt(2), -5*sqrt(2)/2)
|
||||
|
||||
# Circumcircle
|
||||
assert t1.circumcircle.center == Point(2.5, 2.5)
|
||||
|
||||
# Medians + Centroid
|
||||
m = t1.medians
|
||||
assert t1.centroid == Point(Rational(5, 3), Rational(5, 3))
|
||||
assert m[p1] == Segment(p1, Point(Rational(5, 2), Rational(5, 2)))
|
||||
assert t3.medians[p1] == Segment(p1, Point(x1/2, x1/2))
|
||||
assert intersection(m[p1], m[p2], m[p3]) == [t1.centroid]
|
||||
assert t1.medial == Triangle(Point(2.5, 0), Point(0, 2.5), Point(2.5, 2.5))
|
||||
|
||||
# Nine-point circle
|
||||
assert t1.nine_point_circle == Circle(Point(2.5, 0),
|
||||
Point(0, 2.5), Point(2.5, 2.5))
|
||||
assert t1.nine_point_circle == Circle(Point(0, 0),
|
||||
Point(0, 2.5), Point(2.5, 2.5))
|
||||
|
||||
# Perpendicular
|
||||
altitudes = t1.altitudes
|
||||
assert altitudes[p1] == Segment(p1, Point(Rational(5, 2), Rational(5, 2)))
|
||||
assert altitudes[p2].equals(s1[0])
|
||||
assert altitudes[p3] == s1[2]
|
||||
assert t1.orthocenter == p1
|
||||
t = S('''Triangle(
|
||||
Point(100080156402737/5000000000000, 79782624633431/500000000000),
|
||||
Point(39223884078253/2000000000000, 156345163124289/1000000000000),
|
||||
Point(31241359188437/1250000000000, 338338270939941/1000000000000000))''')
|
||||
assert t.orthocenter == S('''Point(-780660869050599840216997'''
|
||||
'''79471538701955848721853/80368430960602242240789074233100000000000000,'''
|
||||
'''20151573611150265741278060334545897615974257/16073686192120448448157'''
|
||||
'''8148466200000000000)''')
|
||||
|
||||
# Ensure
|
||||
assert len(intersection(*bisectors.values())) == 1
|
||||
assert len(intersection(*altitudes.values())) == 1
|
||||
assert len(intersection(*m.values())) == 1
|
||||
|
||||
# Distance
|
||||
p1 = Polygon(
|
||||
Point(0, 0), Point(1, 0),
|
||||
Point(1, 1), Point(0, 1))
|
||||
p2 = Polygon(
|
||||
Point(0, Rational(5)/4), Point(1, Rational(5)/4),
|
||||
Point(1, Rational(9)/4), Point(0, Rational(9)/4))
|
||||
p3 = Polygon(
|
||||
Point(1, 2), Point(2, 2),
|
||||
Point(2, 1))
|
||||
p4 = Polygon(
|
||||
Point(1, 1), Point(Rational(6)/5, 1),
|
||||
Point(1, Rational(6)/5))
|
||||
pt1 = Point(half, half)
|
||||
pt2 = Point(1, 1)
|
||||
|
||||
'''Polygon to Point'''
|
||||
assert p1.distance(pt1) == half
|
||||
assert p1.distance(pt2) == 0
|
||||
assert p2.distance(pt1) == Rational(3)/4
|
||||
assert p3.distance(pt2) == sqrt(2)/2
|
||||
|
||||
'''Polygon to Polygon'''
|
||||
# p1.distance(p2) emits a warning
|
||||
with warns(UserWarning, \
|
||||
match="Polygons may intersect producing erroneous output"):
|
||||
assert p1.distance(p2) == half/2
|
||||
|
||||
assert p1.distance(p3) == sqrt(2)/2
|
||||
|
||||
# p3.distance(p4) emits a warning
|
||||
with warns(UserWarning, \
|
||||
match="Polygons may intersect producing erroneous output"):
|
||||
assert p3.distance(p4) == (sqrt(2)/2 - sqrt(Rational(2)/25)/2)
|
||||
|
||||
|
||||
def test_convex_hull():
|
||||
p = [Point(-5, -1), Point(-2, 1), Point(-2, -1), Point(-1, -3), \
|
||||
Point(0, 0), Point(1, 1), Point(2, 2), Point(2, -1), Point(3, 1), \
|
||||
Point(4, -1), Point(6, 2)]
|
||||
ch = Polygon(p[0], p[3], p[9], p[10], p[6], p[1])
|
||||
#test handling of duplicate points
|
||||
p.append(p[3])
|
||||
|
||||
#more than 3 collinear points
|
||||
another_p = [Point(-45, -85), Point(-45, 85), Point(-45, 26), \
|
||||
Point(-45, -24)]
|
||||
ch2 = Segment(another_p[0], another_p[1])
|
||||
|
||||
assert convex_hull(*another_p) == ch2
|
||||
assert convex_hull(*p) == ch
|
||||
assert convex_hull(p[0]) == p[0]
|
||||
assert convex_hull(p[0], p[1]) == Segment(p[0], p[1])
|
||||
|
||||
# no unique points
|
||||
assert convex_hull(*[p[-1]]*3) == p[-1]
|
||||
|
||||
# collection of items
|
||||
assert convex_hull(*[Point(0, 0), \
|
||||
Segment(Point(1, 0), Point(1, 1)), \
|
||||
RegularPolygon(Point(2, 0), 2, 4)]) == \
|
||||
Polygon(Point(0, 0), Point(2, -2), Point(4, 0), Point(2, 2))
|
||||
|
||||
|
||||
def test_encloses():
|
||||
# square with a dimpled left side
|
||||
s = Polygon(Point(0, 0), Point(1, 0), Point(1, 1), Point(0, 1), \
|
||||
Point(S.Half, S.Half))
|
||||
# the following is True if the polygon isn't treated as closing on itself
|
||||
assert s.encloses(Point(0, S.Half)) is False
|
||||
assert s.encloses(Point(S.Half, S.Half)) is False # it's a vertex
|
||||
assert s.encloses(Point(Rational(3, 4), S.Half)) is True
|
||||
|
||||
|
||||
def test_triangle_kwargs():
|
||||
assert Triangle(sss=(3, 4, 5)) == \
|
||||
Triangle(Point(0, 0), Point(3, 0), Point(3, 4))
|
||||
assert Triangle(asa=(30, 2, 30)) == \
|
||||
Triangle(Point(0, 0), Point(2, 0), Point(1, sqrt(3)/3))
|
||||
assert Triangle(sas=(1, 45, 2)) == \
|
||||
Triangle(Point(0, 0), Point(2, 0), Point(sqrt(2)/2, sqrt(2)/2))
|
||||
assert Triangle(sss=(1, 2, 5)) is None
|
||||
assert deg(rad(180)) == 180
|
||||
|
||||
|
||||
def test_transform():
|
||||
pts = [Point(0, 0), Point(S.Half, Rational(1, 4)), Point(1, 1)]
|
||||
pts_out = [Point(-4, -10), Point(-3, Rational(-37, 4)), Point(-2, -7)]
|
||||
assert Triangle(*pts).scale(2, 3, (4, 5)) == Triangle(*pts_out)
|
||||
assert RegularPolygon((0, 0), 1, 4).scale(2, 3, (4, 5)) == \
|
||||
Polygon(Point(-2, -10), Point(-4, -7), Point(-6, -10), Point(-4, -13))
|
||||
# Checks for symmetric scaling
|
||||
assert RegularPolygon((0, 0), 1, 4).scale(2, 2) == \
|
||||
RegularPolygon(Point2D(0, 0), 2, 4, 0)
|
||||
|
||||
def test_reflect():
|
||||
x = Symbol('x', real=True)
|
||||
y = Symbol('y', real=True)
|
||||
b = Symbol('b')
|
||||
m = Symbol('m')
|
||||
l = Line((0, b), slope=m)
|
||||
p = Point(x, y)
|
||||
r = p.reflect(l)
|
||||
dp = l.perpendicular_segment(p).length
|
||||
dr = l.perpendicular_segment(r).length
|
||||
|
||||
assert verify_numerically(dp, dr)
|
||||
|
||||
assert Polygon((1, 0), (2, 0), (2, 2)).reflect(Line((3, 0), slope=oo)) \
|
||||
== Triangle(Point(5, 0), Point(4, 0), Point(4, 2))
|
||||
assert Polygon((1, 0), (2, 0), (2, 2)).reflect(Line((0, 3), slope=oo)) \
|
||||
== Triangle(Point(-1, 0), Point(-2, 0), Point(-2, 2))
|
||||
assert Polygon((1, 0), (2, 0), (2, 2)).reflect(Line((0, 3), slope=0)) \
|
||||
== Triangle(Point(1, 6), Point(2, 6), Point(2, 4))
|
||||
assert Polygon((1, 0), (2, 0), (2, 2)).reflect(Line((3, 0), slope=0)) \
|
||||
== Triangle(Point(1, 0), Point(2, 0), Point(2, -2))
|
||||
|
||||
def test_bisectors():
|
||||
p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1)
|
||||
p = Polygon(Point(0, 0), Point(2, 0), Point(1, 1), Point(0, 3))
|
||||
q = Polygon(Point(1, 0), Point(2, 0), Point(3, 3), Point(-1, 5))
|
||||
poly = Polygon(Point(3, 4), Point(0, 0), Point(8, 7), Point(-1, 1), Point(19, -19))
|
||||
t = Triangle(p1, p2, p3)
|
||||
assert t.bisectors()[p2] == Segment(Point(1, 0), Point(0, sqrt(2) - 1))
|
||||
assert p.bisectors()[Point2D(0, 3)] == Ray2D(Point2D(0, 3), \
|
||||
Point2D(sin(acos(2*sqrt(5)/5)/2), 3 - cos(acos(2*sqrt(5)/5)/2)))
|
||||
assert q.bisectors()[Point2D(-1, 5)] == \
|
||||
Ray2D(Point2D(-1, 5), Point2D(-1 + sqrt(29)*(5*sin(acos(9*sqrt(145)/145)/2) + \
|
||||
2*cos(acos(9*sqrt(145)/145)/2))/29, sqrt(29)*(-5*cos(acos(9*sqrt(145)/145)/2) + \
|
||||
2*sin(acos(9*sqrt(145)/145)/2))/29 + 5))
|
||||
assert poly.bisectors()[Point2D(-1, 1)] == Ray2D(Point2D(-1, 1), \
|
||||
Point2D(-1 + sin(acos(sqrt(26)/26)/2 + pi/4), 1 - sin(-acos(sqrt(26)/26)/2 + pi/4)))
|
||||
|
||||
def test_incenter():
|
||||
assert Triangle(Point(0, 0), Point(1, 0), Point(0, 1)).incenter \
|
||||
== Point(1 - sqrt(2)/2, 1 - sqrt(2)/2)
|
||||
|
||||
def test_inradius():
|
||||
assert Triangle(Point(0, 0), Point(4, 0), Point(0, 3)).inradius == 1
|
||||
|
||||
def test_incircle():
|
||||
assert Triangle(Point(0, 0), Point(2, 0), Point(0, 2)).incircle \
|
||||
== Circle(Point(2 - sqrt(2), 2 - sqrt(2)), 2 - sqrt(2))
|
||||
|
||||
def test_exradii():
|
||||
t = Triangle(Point(0, 0), Point(6, 0), Point(0, 2))
|
||||
assert t.exradii[t.sides[2]] == (-2 + sqrt(10))
|
||||
|
||||
def test_medians():
|
||||
t = Triangle(Point(0, 0), Point(1, 0), Point(0, 1))
|
||||
assert t.medians[Point(0, 0)] == Segment(Point(0, 0), Point(S.Half, S.Half))
|
||||
|
||||
def test_medial():
|
||||
assert Triangle(Point(0, 0), Point(1, 0), Point(0, 1)).medial \
|
||||
== Triangle(Point(S.Half, 0), Point(S.Half, S.Half), Point(0, S.Half))
|
||||
|
||||
def test_nine_point_circle():
|
||||
assert Triangle(Point(0, 0), Point(1, 0), Point(0, 1)).nine_point_circle \
|
||||
== Circle(Point2D(Rational(1, 4), Rational(1, 4)), sqrt(2)/4)
|
||||
|
||||
def test_eulerline():
|
||||
assert Triangle(Point(0, 0), Point(1, 0), Point(0, 1)).eulerline \
|
||||
== Line(Point2D(0, 0), Point2D(S.Half, S.Half))
|
||||
assert Triangle(Point(0, 0), Point(10, 0), Point(5, 5*sqrt(3))).eulerline \
|
||||
== Point2D(5, 5*sqrt(3)/3)
|
||||
assert Triangle(Point(4, -6), Point(4, -1), Point(-3, 3)).eulerline \
|
||||
== Line(Point2D(Rational(64, 7), 3), Point2D(Rational(-29, 14), Rational(-7, 2)))
|
||||
|
||||
def test_intersection():
|
||||
poly1 = Triangle(Point(0, 0), Point(1, 0), Point(0, 1))
|
||||
poly2 = Polygon(Point(0, 1), Point(-5, 0),
|
||||
Point(0, -4), Point(0, Rational(1, 5)),
|
||||
Point(S.Half, -0.1), Point(1, 0), Point(0, 1))
|
||||
|
||||
assert poly1.intersection(poly2) == [Point2D(Rational(1, 3), 0),
|
||||
Segment(Point(0, Rational(1, 5)), Point(0, 0)),
|
||||
Segment(Point(1, 0), Point(0, 1))]
|
||||
assert poly2.intersection(poly1) == [Point(Rational(1, 3), 0),
|
||||
Segment(Point(0, 0), Point(0, Rational(1, 5))),
|
||||
Segment(Point(1, 0), Point(0, 1))]
|
||||
assert poly1.intersection(Point(0, 0)) == [Point(0, 0)]
|
||||
assert poly1.intersection(Point(-12, -43)) == []
|
||||
assert poly2.intersection(Line((-12, 0), (12, 0))) == [Point(-5, 0),
|
||||
Point(0, 0), Point(Rational(1, 3), 0), Point(1, 0)]
|
||||
assert poly2.intersection(Line((-12, 12), (12, 12))) == []
|
||||
assert poly2.intersection(Ray((-3, 4), (1, 0))) == [Segment(Point(1, 0),
|
||||
Point(0, 1))]
|
||||
assert poly2.intersection(Circle((0, -1), 1)) == [Point(0, -2),
|
||||
Point(0, 0)]
|
||||
assert poly1.intersection(poly1) == [Segment(Point(0, 0), Point(1, 0)),
|
||||
Segment(Point(0, 1), Point(0, 0)), Segment(Point(1, 0), Point(0, 1))]
|
||||
assert poly2.intersection(poly2) == [Segment(Point(-5, 0), Point(0, -4)),
|
||||
Segment(Point(0, -4), Point(0, Rational(1, 5))),
|
||||
Segment(Point(0, Rational(1, 5)), Point(S.Half, Rational(-1, 10))),
|
||||
Segment(Point(0, 1), Point(-5, 0)),
|
||||
Segment(Point(S.Half, Rational(-1, 10)), Point(1, 0)),
|
||||
Segment(Point(1, 0), Point(0, 1))]
|
||||
assert poly2.intersection(Triangle(Point(0, 1), Point(1, 0), Point(-1, 1))) \
|
||||
== [Point(Rational(-5, 7), Rational(6, 7)), Segment(Point2D(0, 1), Point(1, 0))]
|
||||
assert poly1.intersection(RegularPolygon((-12, -15), 3, 3)) == []
|
||||
|
||||
|
||||
def test_parameter_value():
|
||||
t = Symbol('t')
|
||||
sq = Polygon((0, 0), (0, 1), (1, 1), (1, 0))
|
||||
assert sq.parameter_value((0.5, 1), t) == {t: Rational(3, 8)}
|
||||
q = Polygon((0, 0), (2, 1), (2, 4), (4, 0))
|
||||
assert q.parameter_value((4, 0), t) == {t: -6 + 3*sqrt(5)} # ~= 0.708
|
||||
|
||||
raises(ValueError, lambda: sq.parameter_value((5, 6), t))
|
||||
raises(ValueError, lambda: sq.parameter_value(Circle(Point(0, 0), 1), t))
|
||||
|
||||
|
||||
def test_issue_12966():
|
||||
poly = Polygon(Point(0, 0), Point(0, 10), Point(5, 10), Point(5, 5),
|
||||
Point(10, 5), Point(10, 0))
|
||||
t = Symbol('t')
|
||||
pt = poly.arbitrary_point(t)
|
||||
DELTA = 5/poly.perimeter
|
||||
assert [pt.subs(t, DELTA*i) for i in range(int(1/DELTA))] == [
|
||||
Point(0, 0), Point(0, 5), Point(0, 10), Point(5, 10),
|
||||
Point(5, 5), Point(10, 5), Point(10, 0), Point(5, 0)]
|
||||
|
||||
|
||||
def test_second_moment_of_area():
|
||||
x, y = symbols('x, y')
|
||||
# triangle
|
||||
p1, p2, p3 = [(0, 0), (4, 0), (0, 2)]
|
||||
p = (0, 0)
|
||||
# equation of hypotenuse
|
||||
eq_y = (1-x/4)*2
|
||||
I_yy = integrate((x**2) * (integrate(1, (y, 0, eq_y))), (x, 0, 4))
|
||||
I_xx = integrate(1 * (integrate(y**2, (y, 0, eq_y))), (x, 0, 4))
|
||||
I_xy = integrate(x * (integrate(y, (y, 0, eq_y))), (x, 0, 4))
|
||||
|
||||
triangle = Polygon(p1, p2, p3)
|
||||
|
||||
assert (I_xx - triangle.second_moment_of_area(p)[0]) == 0
|
||||
assert (I_yy - triangle.second_moment_of_area(p)[1]) == 0
|
||||
assert (I_xy - triangle.second_moment_of_area(p)[2]) == 0
|
||||
|
||||
# rectangle
|
||||
p1, p2, p3, p4=[(0, 0), (4, 0), (4, 2), (0, 2)]
|
||||
I_yy = integrate((x**2) * integrate(1, (y, 0, 2)), (x, 0, 4))
|
||||
I_xx = integrate(1 * integrate(y**2, (y, 0, 2)), (x, 0, 4))
|
||||
I_xy = integrate(x * integrate(y, (y, 0, 2)), (x, 0, 4))
|
||||
|
||||
rectangle = Polygon(p1, p2, p3, p4)
|
||||
|
||||
assert (I_xx - rectangle.second_moment_of_area(p)[0]) == 0
|
||||
assert (I_yy - rectangle.second_moment_of_area(p)[1]) == 0
|
||||
assert (I_xy - rectangle.second_moment_of_area(p)[2]) == 0
|
||||
|
||||
|
||||
r = RegularPolygon(Point(0, 0), 5, 3)
|
||||
assert r.second_moment_of_area() == (1875*sqrt(3)/S(32), 1875*sqrt(3)/S(32), 0)
|
||||
|
||||
|
||||
def test_first_moment():
|
||||
a, b = symbols('a, b', positive=True)
|
||||
# rectangle
|
||||
p1 = Polygon((0, 0), (a, 0), (a, b), (0, b))
|
||||
assert p1.first_moment_of_area() == (a*b**2/8, a**2*b/8)
|
||||
assert p1.first_moment_of_area((a/3, b/4)) == (-3*a*b**2/32, -a**2*b/9)
|
||||
|
||||
p1 = Polygon((0, 0), (40, 0), (40, 30), (0, 30))
|
||||
assert p1.first_moment_of_area() == (4500, 6000)
|
||||
|
||||
# triangle
|
||||
p2 = Polygon((0, 0), (a, 0), (a/2, b))
|
||||
assert p2.first_moment_of_area() == (4*a*b**2/81, a**2*b/24)
|
||||
assert p2.first_moment_of_area((a/8, b/6)) == (-25*a*b**2/648, -5*a**2*b/768)
|
||||
|
||||
p2 = Polygon((0, 0), (12, 0), (12, 30))
|
||||
assert p2.first_moment_of_area() == (S(1600)/3, -S(640)/3)
|
||||
|
||||
|
||||
def test_section_modulus_and_polar_second_moment_of_area():
|
||||
a, b = symbols('a, b', positive=True)
|
||||
x, y = symbols('x, y')
|
||||
rectangle = Polygon((0, b), (0, 0), (a, 0), (a, b))
|
||||
assert rectangle.section_modulus(Point(x, y)) == (a*b**3/12/(-b/2 + y), a**3*b/12/(-a/2 + x))
|
||||
assert rectangle.polar_second_moment_of_area() == a**3*b/12 + a*b**3/12
|
||||
|
||||
convex = RegularPolygon((0, 0), 1, 6)
|
||||
assert convex.section_modulus() == (Rational(5, 8), sqrt(3)*Rational(5, 16))
|
||||
assert convex.polar_second_moment_of_area() == 5*sqrt(3)/S(8)
|
||||
|
||||
concave = Polygon((0, 0), (1, 8), (3, 4), (4, 6), (7, 1))
|
||||
assert concave.section_modulus() == (Rational(-6371, 429), Rational(-9778, 519))
|
||||
assert concave.polar_second_moment_of_area() == Rational(-38669, 252)
|
||||
|
||||
|
||||
def test_cut_section():
|
||||
# concave polygon
|
||||
p = Polygon((-1, -1), (1, Rational(5, 2)), (2, 1), (3, Rational(5, 2)), (4, 2), (5, 3), (-1, 3))
|
||||
l = Line((0, 0), (Rational(9, 2), 3))
|
||||
p1 = p.cut_section(l)[0]
|
||||
p2 = p.cut_section(l)[1]
|
||||
assert p1 == Polygon(
|
||||
Point2D(Rational(-9, 13), Rational(-6, 13)), Point2D(1, Rational(5, 2)), Point2D(Rational(24, 13), Rational(16, 13)),
|
||||
Point2D(Rational(12, 5), Rational(8, 5)), Point2D(3, Rational(5, 2)), Point2D(Rational(24, 7), Rational(16, 7)),
|
||||
Point2D(Rational(9, 2), 3), Point2D(-1, 3), Point2D(-1, Rational(-2, 3)))
|
||||
assert p2 == Polygon(Point2D(-1, -1), Point2D(Rational(-9, 13), Rational(-6, 13)), Point2D(Rational(24, 13), Rational(16, 13)),
|
||||
Point2D(2, 1), Point2D(Rational(12, 5), Rational(8, 5)), Point2D(Rational(24, 7), Rational(16, 7)), Point2D(4, 2), Point2D(5, 3),
|
||||
Point2D(Rational(9, 2), 3), Point2D(-1, Rational(-2, 3)))
|
||||
|
||||
# convex polygon
|
||||
p = RegularPolygon(Point2D(0, 0), 6, 6)
|
||||
s = p.cut_section(Line((0, 0), slope=1))
|
||||
assert s[0] == Polygon(Point2D(-3*sqrt(3) + 9, -3*sqrt(3) + 9), Point2D(3, 3*sqrt(3)),
|
||||
Point2D(-3, 3*sqrt(3)), Point2D(-6, 0), Point2D(-9 + 3*sqrt(3), -9 + 3*sqrt(3)))
|
||||
assert s[1] == Polygon(Point2D(6, 0), Point2D(-3*sqrt(3) + 9, -3*sqrt(3) + 9),
|
||||
Point2D(-9 + 3*sqrt(3), -9 + 3*sqrt(3)), Point2D(-3, -3*sqrt(3)), Point2D(3, -3*sqrt(3)))
|
||||
|
||||
# case where line does not intersects but coincides with the edge of polygon
|
||||
a, b = 20, 10
|
||||
t1, t2, t3, t4 = [(0, b), (0, 0), (a, 0), (a, b)]
|
||||
p = Polygon(t1, t2, t3, t4)
|
||||
p1, p2 = p.cut_section(Line((0, b), slope=0))
|
||||
assert p1 == None
|
||||
assert p2 == Polygon(Point2D(0, 10), Point2D(0, 0), Point2D(20, 0), Point2D(20, 10))
|
||||
|
||||
p3, p4 = p.cut_section(Line((0, 0), slope=0))
|
||||
assert p3 == Polygon(Point2D(0, 10), Point2D(0, 0), Point2D(20, 0), Point2D(20, 10))
|
||||
assert p4 == None
|
||||
|
||||
# case where the line does not intersect with a polygon at all
|
||||
raises(ValueError, lambda: p.cut_section(Line((0, a), slope=0)))
|
||||
|
||||
def test_type_of_triangle():
|
||||
# Isoceles triangle
|
||||
p1 = Polygon(Point(0, 0), Point(5, 0), Point(2, 4))
|
||||
assert p1.is_isosceles() == True
|
||||
assert p1.is_scalene() == False
|
||||
assert p1.is_equilateral() == False
|
||||
|
||||
# Scalene triangle
|
||||
p2 = Polygon (Point(0, 0), Point(0, 2), Point(4, 0))
|
||||
assert p2.is_isosceles() == False
|
||||
assert p2.is_scalene() == True
|
||||
assert p2.is_equilateral() == False
|
||||
|
||||
# Equilateral triangle
|
||||
p3 = Polygon(Point(0, 0), Point(6, 0), Point(3, sqrt(27)))
|
||||
assert p3.is_isosceles() == True
|
||||
assert p3.is_scalene() == False
|
||||
assert p3.is_equilateral() == True
|
||||
|
||||
def test_do_poly_distance():
|
||||
# Non-intersecting polygons
|
||||
square1 = Polygon (Point(0, 0), Point(0, 1), Point(1, 1), Point(1, 0))
|
||||
triangle1 = Polygon(Point(1, 2), Point(2, 2), Point(2, 1))
|
||||
assert square1._do_poly_distance(triangle1) == sqrt(2)/2
|
||||
|
||||
# Polygons which sides intersect
|
||||
square2 = Polygon(Point(1, 0), Point(2, 0), Point(2, 1), Point(1, 1))
|
||||
with warns(UserWarning, \
|
||||
match="Polygons may intersect producing erroneous output", test_stacklevel=False):
|
||||
assert square1._do_poly_distance(square2) == 0
|
||||
|
||||
# Polygons which bodies intersect
|
||||
triangle2 = Polygon(Point(0, -1), Point(2, -1), Point(S.Half, S.Half))
|
||||
with warns(UserWarning, \
|
||||
match="Polygons may intersect producing erroneous output", test_stacklevel=False):
|
||||
assert triangle2._do_poly_distance(square1) == 0
|
||||
@@ -0,0 +1,170 @@
|
||||
import pytest
|
||||
from sympy.core.numbers import Float
|
||||
from sympy.core.function import (Derivative, Function)
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import Symbol
|
||||
from sympy.functions import exp, cos, sin, tan, cosh, sinh
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.geometry import Point, Point2D, Line, Polygon, Segment, convex_hull,\
|
||||
intersection, centroid, Point3D, Line3D, Ray, Ellipse
|
||||
from sympy.geometry.util import idiff, closest_points, farthest_points, _ordered_points, are_coplanar
|
||||
from sympy.solvers.solvers import solve
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
|
||||
def test_idiff():
|
||||
x = Symbol('x', real=True)
|
||||
y = Symbol('y', real=True)
|
||||
t = Symbol('t', real=True)
|
||||
f = Function('f')
|
||||
g = Function('g')
|
||||
# the use of idiff in ellipse also provides coverage
|
||||
circ = x**2 + y**2 - 4
|
||||
ans = -3*x*(x**2/y**2 + 1)/y**3
|
||||
assert ans == idiff(circ, y, x, 3), idiff(circ, y, x, 3)
|
||||
assert ans == idiff(circ, [y], x, 3)
|
||||
assert idiff(circ, y, x, 3) == ans
|
||||
explicit = 12*x/sqrt(-x**2 + 4)**5
|
||||
assert ans.subs(y, solve(circ, y)[0]).equals(explicit)
|
||||
assert True in [sol.diff(x, 3).equals(explicit) for sol in solve(circ, y)]
|
||||
assert idiff(x + t + y, [y, t], x) == -Derivative(t, x) - 1
|
||||
assert idiff(f(x) * exp(f(x)) - x * exp(x), f(x), x) == (x + 1)*exp(x)*exp(-f(x))/(f(x) + 1)
|
||||
assert idiff(f(x) - y * exp(x), [f(x), y], x) == (y + Derivative(y, x))*exp(x)
|
||||
assert idiff(f(x) - y * exp(x), [y, f(x)], x) == -y + Derivative(f(x), x)*exp(-x)
|
||||
assert idiff(f(x) - g(x), [f(x), g(x)], x) == Derivative(g(x), x)
|
||||
# this should be fast
|
||||
fxy = y - (-10*(-sin(x) + 1/x)**2 + tan(x)**2 + 2*cosh(x/10))
|
||||
assert idiff(fxy, y, x) == -20*sin(x)*cos(x) + 2*tan(x)**3 + \
|
||||
2*tan(x) + sinh(x/10)/5 + 20*cos(x)/x - 20*sin(x)/x**2 + 20/x**3
|
||||
|
||||
|
||||
def test_intersection():
|
||||
assert intersection(Point(0, 0)) == []
|
||||
raises(TypeError, lambda: intersection(Point(0, 0), 3))
|
||||
assert intersection(
|
||||
Segment((0, 0), (2, 0)),
|
||||
Segment((-1, 0), (1, 0)),
|
||||
Line((0, 0), (0, 1)), pairwise=True) == [
|
||||
Point(0, 0), Segment((0, 0), (1, 0))]
|
||||
assert intersection(
|
||||
Line((0, 0), (0, 1)),
|
||||
Segment((0, 0), (2, 0)),
|
||||
Segment((-1, 0), (1, 0)), pairwise=True) == [
|
||||
Point(0, 0), Segment((0, 0), (1, 0))]
|
||||
assert intersection(
|
||||
Line((0, 0), (0, 1)),
|
||||
Segment((0, 0), (2, 0)),
|
||||
Segment((-1, 0), (1, 0)),
|
||||
Line((0, 0), slope=1), pairwise=True) == [
|
||||
Point(0, 0), Segment((0, 0), (1, 0))]
|
||||
R = 4.0
|
||||
c = intersection(
|
||||
Ray(Point2D(0.001, -1),
|
||||
Point2D(0.0008, -1.7)),
|
||||
Ellipse(center=Point2D(0, 0), hradius=R, vradius=2.0), pairwise=True)[0].coordinates
|
||||
assert c == pytest.approx(
|
||||
Point2D(0.000714285723396502, -1.99999996811224, evaluate=False).coordinates)
|
||||
# check this is responds to a lower precision parameter
|
||||
R = Float(4, 5)
|
||||
c2 = intersection(
|
||||
Ray(Point2D(0.001, -1),
|
||||
Point2D(0.0008, -1.7)),
|
||||
Ellipse(center=Point2D(0, 0), hradius=R, vradius=2.0), pairwise=True)[0].coordinates
|
||||
assert c2 == pytest.approx(
|
||||
Point2D(0.000714285723396502, -1.99999996811224, evaluate=False).coordinates)
|
||||
assert c[0]._prec == 53
|
||||
assert c2[0]._prec == 20
|
||||
|
||||
|
||||
def test_convex_hull():
|
||||
raises(TypeError, lambda: convex_hull(Point(0, 0), 3))
|
||||
points = [(1, -1), (1, -2), (3, -1), (-5, -2), (15, -4)]
|
||||
assert convex_hull(*points, **{"polygon": False}) == (
|
||||
[Point2D(-5, -2), Point2D(1, -1), Point2D(3, -1), Point2D(15, -4)],
|
||||
[Point2D(-5, -2), Point2D(15, -4)])
|
||||
|
||||
|
||||
def test_centroid():
|
||||
p = Polygon((0, 0), (10, 0), (10, 10))
|
||||
q = p.translate(0, 20)
|
||||
assert centroid(p, q) == Point(20, 40)/3
|
||||
p = Segment((0, 0), (2, 0))
|
||||
q = Segment((0, 0), (2, 2))
|
||||
assert centroid(p, q) == Point(1, -sqrt(2) + 2)
|
||||
assert centroid(Point(0, 0), Point(2, 0)) == Point(2, 0)/2
|
||||
assert centroid(Point(0, 0), Point(0, 0), Point(2, 0)) == Point(2, 0)/3
|
||||
|
||||
|
||||
def test_farthest_points_closest_points():
|
||||
from sympy.core.random import randint
|
||||
from sympy.utilities.iterables import subsets
|
||||
|
||||
for how in (min, max):
|
||||
if how == min:
|
||||
func = closest_points
|
||||
else:
|
||||
func = farthest_points
|
||||
|
||||
raises(ValueError, lambda: func(Point2D(0, 0), Point2D(0, 0)))
|
||||
|
||||
# 3rd pt dx is close and pt is closer to 1st pt
|
||||
p1 = [Point2D(0, 0), Point2D(3, 0), Point2D(1, 1)]
|
||||
# 3rd pt dx is close and pt is closer to 2nd pt
|
||||
p2 = [Point2D(0, 0), Point2D(3, 0), Point2D(2, 1)]
|
||||
# 3rd pt dx is close and but pt is not closer
|
||||
p3 = [Point2D(0, 0), Point2D(3, 0), Point2D(1, 10)]
|
||||
# 3rd pt dx is not closer and it's closer to 2nd pt
|
||||
p4 = [Point2D(0, 0), Point2D(3, 0), Point2D(4, 0)]
|
||||
# 3rd pt dx is not closer and it's closer to 1st pt
|
||||
p5 = [Point2D(0, 0), Point2D(3, 0), Point2D(-1, 0)]
|
||||
# duplicate point doesn't affect outcome
|
||||
dup = [Point2D(0, 0), Point2D(3, 0), Point2D(3, 0), Point2D(-1, 0)]
|
||||
# symbolic
|
||||
x = Symbol('x', positive=True)
|
||||
s = [Point2D(a) for a in ((x, 1), (x + 3, 2), (x + 2, 2))]
|
||||
|
||||
for points in (p1, p2, p3, p4, p5, dup, s):
|
||||
d = how(i.distance(j) for i, j in subsets(set(points), 2))
|
||||
ans = a, b = list(func(*points))[0]
|
||||
assert a.distance(b) == d
|
||||
assert ans == _ordered_points(ans)
|
||||
|
||||
# if the following ever fails, the above tests were not sufficient
|
||||
# and the logical error in the routine should be fixed
|
||||
points = set()
|
||||
while len(points) != 7:
|
||||
points.add(Point2D(randint(1, 100), randint(1, 100)))
|
||||
points = list(points)
|
||||
d = how(i.distance(j) for i, j in subsets(points, 2))
|
||||
ans = a, b = list(func(*points))[0]
|
||||
assert a.distance(b) == d
|
||||
assert ans == _ordered_points(ans)
|
||||
|
||||
# equidistant points
|
||||
a, b, c = (
|
||||
Point2D(0, 0), Point2D(1, 0), Point2D(S.Half, sqrt(3)/2))
|
||||
ans = {_ordered_points((i, j))
|
||||
for i, j in subsets((a, b, c), 2)}
|
||||
assert closest_points(b, c, a) == ans
|
||||
assert farthest_points(b, c, a) == ans
|
||||
|
||||
# unique to farthest
|
||||
points = [(1, 1), (1, 2), (3, 1), (-5, 2), (15, 4)]
|
||||
assert farthest_points(*points) == {
|
||||
(Point2D(-5, 2), Point2D(15, 4))}
|
||||
points = [(1, -1), (1, -2), (3, -1), (-5, -2), (15, -4)]
|
||||
assert farthest_points(*points) == {
|
||||
(Point2D(-5, -2), Point2D(15, -4))}
|
||||
assert farthest_points((1, 1), (0, 0)) == {
|
||||
(Point2D(0, 0), Point2D(1, 1))}
|
||||
raises(ValueError, lambda: farthest_points((1, 1)))
|
||||
|
||||
|
||||
def test_are_coplanar():
|
||||
a = Line3D(Point3D(5, 0, 0), Point3D(1, -1, 1))
|
||||
b = Line3D(Point3D(0, -2, 0), Point3D(3, 1, 1))
|
||||
c = Line3D(Point3D(0, -1, 0), Point3D(5, -1, 9))
|
||||
d = Line(Point2D(0, 3), Point2D(1, 5))
|
||||
|
||||
assert are_coplanar(a, b, c) == False
|
||||
assert are_coplanar(a, d) == False
|
||||
@@ -0,0 +1,731 @@
|
||||
"""Utility functions for geometrical entities.
|
||||
|
||||
Contains
|
||||
========
|
||||
intersection
|
||||
convex_hull
|
||||
closest_points
|
||||
farthest_points
|
||||
are_coplanar
|
||||
are_similar
|
||||
|
||||
"""
|
||||
|
||||
from collections import deque
|
||||
from math import sqrt as _sqrt
|
||||
|
||||
from sympy import nsimplify
|
||||
from .entity import GeometryEntity
|
||||
from .exceptions import GeometryError
|
||||
from .point import Point, Point2D, Point3D
|
||||
from sympy.core.containers import OrderedSet
|
||||
from sympy.core.exprtools import factor_terms
|
||||
from sympy.core.function import Function, expand_mul
|
||||
from sympy.core.numbers import Float
|
||||
from sympy.core.sorting import ordered
|
||||
from sympy.core.symbol import Symbol
|
||||
from sympy.core.singleton import S
|
||||
from sympy.polys.polytools import cancel
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.utilities.iterables import is_sequence
|
||||
|
||||
from mpmath.libmp.libmpf import prec_to_dps
|
||||
|
||||
|
||||
def find(x, equation):
|
||||
"""
|
||||
Checks whether a Symbol matching ``x`` is present in ``equation``
|
||||
or not. If present, the matching symbol is returned, else a
|
||||
ValueError is raised. If ``x`` is a string the matching symbol
|
||||
will have the same name; if ``x`` is a Symbol then it will be
|
||||
returned if found.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.geometry.util import find
|
||||
>>> from sympy import Dummy
|
||||
>>> from sympy.abc import x
|
||||
>>> find('x', x)
|
||||
x
|
||||
>>> find('x', Dummy('x'))
|
||||
_x
|
||||
|
||||
The dummy symbol is returned since it has a matching name:
|
||||
|
||||
>>> _.name == 'x'
|
||||
True
|
||||
>>> find(x, Dummy('x'))
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: could not find x
|
||||
"""
|
||||
|
||||
free = equation.free_symbols
|
||||
xs = [i for i in free if (i.name if isinstance(x, str) else i) == x]
|
||||
if not xs:
|
||||
raise ValueError('could not find %s' % x)
|
||||
if len(xs) != 1:
|
||||
raise ValueError('ambiguous %s' % x)
|
||||
return xs[0]
|
||||
|
||||
|
||||
def _ordered_points(p):
|
||||
"""Return the tuple of points sorted numerically according to args"""
|
||||
return tuple(sorted(p, key=lambda x: x.args))
|
||||
|
||||
|
||||
def are_coplanar(*e):
|
||||
""" Returns True if the given entities are coplanar otherwise False
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
e: entities to be checked for being coplanar
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
Boolean
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Point3D, Line3D
|
||||
>>> from sympy.geometry.util import are_coplanar
|
||||
>>> a = Line3D(Point3D(5, 0, 0), Point3D(1, -1, 1))
|
||||
>>> b = Line3D(Point3D(0, -2, 0), Point3D(3, 1, 1))
|
||||
>>> c = Line3D(Point3D(0, -1, 0), Point3D(5, -1, 9))
|
||||
>>> are_coplanar(a, b, c)
|
||||
False
|
||||
|
||||
"""
|
||||
from .line import LinearEntity3D
|
||||
from .plane import Plane
|
||||
# XXX update tests for coverage
|
||||
|
||||
e = set(e)
|
||||
# first work with a Plane if present
|
||||
for i in list(e):
|
||||
if isinstance(i, Plane):
|
||||
e.remove(i)
|
||||
return all(p.is_coplanar(i) for p in e)
|
||||
|
||||
if all(isinstance(i, Point3D) for i in e):
|
||||
if len(e) < 3:
|
||||
return False
|
||||
|
||||
# remove pts that are collinear with 2 pts
|
||||
a, b = e.pop(), e.pop()
|
||||
for i in list(e):
|
||||
if Point3D.are_collinear(a, b, i):
|
||||
e.remove(i)
|
||||
|
||||
if not e:
|
||||
return False
|
||||
else:
|
||||
# define a plane
|
||||
p = Plane(a, b, e.pop())
|
||||
for i in e:
|
||||
if i not in p:
|
||||
return False
|
||||
return True
|
||||
else:
|
||||
pt3d = []
|
||||
for i in e:
|
||||
if isinstance(i, Point3D):
|
||||
pt3d.append(i)
|
||||
elif isinstance(i, LinearEntity3D):
|
||||
pt3d.extend(i.args)
|
||||
elif isinstance(i, GeometryEntity): # XXX we should have a GeometryEntity3D class so we can tell the difference between 2D and 3D -- here we just want to deal with 2D objects; if new 3D objects are encountered that we didn't handle above, an error should be raised
|
||||
# all 2D objects have some Point that defines them; so convert those points to 3D pts by making z=0
|
||||
for p in i.args:
|
||||
if isinstance(p, Point):
|
||||
pt3d.append(Point3D(*(p.args + (0,))))
|
||||
return are_coplanar(*pt3d)
|
||||
|
||||
|
||||
def are_similar(e1, e2):
|
||||
"""Are two geometrical entities similar.
|
||||
|
||||
Can one geometrical entity be uniformly scaled to the other?
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
e1 : GeometryEntity
|
||||
e2 : GeometryEntity
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
are_similar : boolean
|
||||
|
||||
Raises
|
||||
======
|
||||
|
||||
GeometryError
|
||||
When `e1` and `e2` cannot be compared.
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
If the two objects are equal then they are similar.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.geometry.entity.GeometryEntity.is_similar
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Point, Circle, Triangle, are_similar
|
||||
>>> c1, c2 = Circle(Point(0, 0), 4), Circle(Point(1, 4), 3)
|
||||
>>> t1 = Triangle(Point(0, 0), Point(1, 0), Point(0, 1))
|
||||
>>> t2 = Triangle(Point(0, 0), Point(2, 0), Point(0, 2))
|
||||
>>> t3 = Triangle(Point(0, 0), Point(3, 0), Point(0, 1))
|
||||
>>> are_similar(t1, t2)
|
||||
True
|
||||
>>> are_similar(t1, t3)
|
||||
False
|
||||
|
||||
"""
|
||||
if e1 == e2:
|
||||
return True
|
||||
is_similar1 = getattr(e1, 'is_similar', None)
|
||||
if is_similar1:
|
||||
return is_similar1(e2)
|
||||
is_similar2 = getattr(e2, 'is_similar', None)
|
||||
if is_similar2:
|
||||
return is_similar2(e1)
|
||||
n1 = e1.__class__.__name__
|
||||
n2 = e2.__class__.__name__
|
||||
raise GeometryError(
|
||||
"Cannot test similarity between %s and %s" % (n1, n2))
|
||||
|
||||
|
||||
def centroid(*args):
|
||||
"""Find the centroid (center of mass) of the collection containing only Points,
|
||||
Segments or Polygons. The centroid is the weighted average of the individual centroid
|
||||
where the weights are the lengths (of segments) or areas (of polygons).
|
||||
Overlapping regions will add to the weight of that region.
|
||||
|
||||
If there are no objects (or a mixture of objects) then None is returned.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.geometry.point.Point, sympy.geometry.line.Segment,
|
||||
sympy.geometry.polygon.Polygon
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Point, Segment, Polygon
|
||||
>>> from sympy.geometry.util import centroid
|
||||
>>> p = Polygon((0, 0), (10, 0), (10, 10))
|
||||
>>> q = p.translate(0, 20)
|
||||
>>> p.centroid, q.centroid
|
||||
(Point2D(20/3, 10/3), Point2D(20/3, 70/3))
|
||||
>>> centroid(p, q)
|
||||
Point2D(20/3, 40/3)
|
||||
>>> p, q = Segment((0, 0), (2, 0)), Segment((0, 0), (2, 2))
|
||||
>>> centroid(p, q)
|
||||
Point2D(1, 2 - sqrt(2))
|
||||
>>> centroid(Point(0, 0), Point(2, 0))
|
||||
Point2D(1, 0)
|
||||
|
||||
Stacking 3 polygons on top of each other effectively triples the
|
||||
weight of that polygon:
|
||||
|
||||
>>> p = Polygon((0, 0), (1, 0), (1, 1), (0, 1))
|
||||
>>> q = Polygon((1, 0), (3, 0), (3, 1), (1, 1))
|
||||
>>> centroid(p, q)
|
||||
Point2D(3/2, 1/2)
|
||||
>>> centroid(p, p, p, q) # centroid x-coord shifts left
|
||||
Point2D(11/10, 1/2)
|
||||
|
||||
Stacking the squares vertically above and below p has the same
|
||||
effect:
|
||||
|
||||
>>> centroid(p, p.translate(0, 1), p.translate(0, -1), q)
|
||||
Point2D(11/10, 1/2)
|
||||
|
||||
"""
|
||||
from .line import Segment
|
||||
from .polygon import Polygon
|
||||
if args:
|
||||
if all(isinstance(g, Point) for g in args):
|
||||
c = Point(0, 0)
|
||||
for g in args:
|
||||
c += g
|
||||
den = len(args)
|
||||
elif all(isinstance(g, Segment) for g in args):
|
||||
c = Point(0, 0)
|
||||
L = 0
|
||||
for g in args:
|
||||
l = g.length
|
||||
c += g.midpoint*l
|
||||
L += l
|
||||
den = L
|
||||
elif all(isinstance(g, Polygon) for g in args):
|
||||
c = Point(0, 0)
|
||||
A = 0
|
||||
for g in args:
|
||||
a = g.area
|
||||
c += g.centroid*a
|
||||
A += a
|
||||
den = A
|
||||
c /= den
|
||||
return c.func(*[i.simplify() for i in c.args])
|
||||
|
||||
|
||||
def closest_points(*args):
|
||||
"""Return the subset of points from a set of points that were
|
||||
the closest to each other in the 2D plane.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
args
|
||||
A collection of Points on 2D plane.
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
This can only be performed on a set of points whose coordinates can
|
||||
be ordered on the number line. If there are no ties then a single
|
||||
pair of Points will be in the set.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import closest_points, Triangle
|
||||
>>> Triangle(sss=(3, 4, 5)).args
|
||||
(Point2D(0, 0), Point2D(3, 0), Point2D(3, 4))
|
||||
>>> closest_points(*_)
|
||||
{(Point2D(0, 0), Point2D(3, 0))}
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://www.cs.mcgill.ca/~cs251/ClosestPair/ClosestPairPS.html
|
||||
|
||||
.. [2] Sweep line algorithm
|
||||
https://en.wikipedia.org/wiki/Sweep_line_algorithm
|
||||
|
||||
"""
|
||||
p = [Point2D(i) for i in set(args)]
|
||||
if len(p) < 2:
|
||||
raise ValueError('At least 2 distinct points must be given.')
|
||||
|
||||
try:
|
||||
p.sort(key=lambda x: x.args)
|
||||
except TypeError:
|
||||
raise ValueError("The points could not be sorted.")
|
||||
|
||||
if not all(i.is_Rational for j in p for i in j.args):
|
||||
def hypot(x, y):
|
||||
arg = x*x + y*y
|
||||
if arg.is_Rational:
|
||||
return _sqrt(arg)
|
||||
return sqrt(arg)
|
||||
else:
|
||||
from math import hypot
|
||||
|
||||
rv = [(0, 1)]
|
||||
best_dist = hypot(p[1].x - p[0].x, p[1].y - p[0].y)
|
||||
left = 0
|
||||
box = deque([0, 1])
|
||||
for i in range(2, len(p)):
|
||||
while left < i and p[i][0] - p[left][0] > best_dist:
|
||||
box.popleft()
|
||||
left += 1
|
||||
|
||||
for j in box:
|
||||
d = hypot(p[i].x - p[j].x, p[i].y - p[j].y)
|
||||
if d < best_dist:
|
||||
rv = [(j, i)]
|
||||
elif d == best_dist:
|
||||
rv.append((j, i))
|
||||
else:
|
||||
continue
|
||||
best_dist = d
|
||||
box.append(i)
|
||||
|
||||
return {tuple([p[i] for i in pair]) for pair in rv}
|
||||
|
||||
|
||||
def convex_hull(*args, polygon=True):
|
||||
"""The convex hull surrounding the Points contained in the list of entities.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
args : a collection of Points, Segments and/or Polygons
|
||||
|
||||
Optional parameters
|
||||
===================
|
||||
|
||||
polygon : Boolean. If True, returns a Polygon, if false a tuple, see below.
|
||||
Default is True.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
convex_hull : Polygon if ``polygon`` is True else as a tuple `(U, L)` where
|
||||
``L`` and ``U`` are the lower and upper hulls, respectively.
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
This can only be performed on a set of points whose coordinates can
|
||||
be ordered on the number line.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.geometry.point.Point, sympy.geometry.polygon.Polygon
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import convex_hull
|
||||
>>> points = [(1, 1), (1, 2), (3, 1), (-5, 2), (15, 4)]
|
||||
>>> convex_hull(*points)
|
||||
Polygon(Point2D(-5, 2), Point2D(1, 1), Point2D(3, 1), Point2D(15, 4))
|
||||
>>> convex_hull(*points, **dict(polygon=False))
|
||||
([Point2D(-5, 2), Point2D(15, 4)],
|
||||
[Point2D(-5, 2), Point2D(1, 1), Point2D(3, 1), Point2D(15, 4)])
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Graham_scan
|
||||
|
||||
.. [2] Andrew's Monotone Chain Algorithm
|
||||
(A.M. Andrew,
|
||||
"Another Efficient Algorithm for Convex Hulls in Two Dimensions", 1979)
|
||||
https://web.archive.org/web/20210511015444/http://geomalgorithms.com/a10-_hull-1.html
|
||||
|
||||
"""
|
||||
from .line import Segment
|
||||
from .polygon import Polygon
|
||||
p = OrderedSet()
|
||||
for e in args:
|
||||
if not isinstance(e, GeometryEntity):
|
||||
try:
|
||||
e = Point(e)
|
||||
except NotImplementedError:
|
||||
raise ValueError('%s is not a GeometryEntity and cannot be made into Point' % str(e))
|
||||
if isinstance(e, Point):
|
||||
p.add(e)
|
||||
elif isinstance(e, Segment):
|
||||
p.update(e.points)
|
||||
elif isinstance(e, Polygon):
|
||||
p.update(e.vertices)
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
'Convex hull for %s not implemented.' % type(e))
|
||||
|
||||
# make sure all our points are of the same dimension
|
||||
if any(len(x) != 2 for x in p):
|
||||
raise ValueError('Can only compute the convex hull in two dimensions')
|
||||
|
||||
p = list(p)
|
||||
if len(p) == 1:
|
||||
return p[0] if polygon else (p[0], None)
|
||||
elif len(p) == 2:
|
||||
s = Segment(p[0], p[1])
|
||||
return s if polygon else (s, None)
|
||||
|
||||
def _orientation(p, q, r):
|
||||
'''Return positive if p-q-r are clockwise, neg if ccw, zero if
|
||||
collinear.'''
|
||||
return (q.y - p.y)*(r.x - p.x) - (q.x - p.x)*(r.y - p.y)
|
||||
|
||||
# scan to find upper and lower convex hulls of a set of 2d points.
|
||||
U = []
|
||||
L = []
|
||||
try:
|
||||
p.sort(key=lambda x: x.args)
|
||||
except TypeError:
|
||||
raise ValueError("The points could not be sorted.")
|
||||
for p_i in p:
|
||||
while len(U) > 1 and _orientation(U[-2], U[-1], p_i) <= 0:
|
||||
U.pop()
|
||||
while len(L) > 1 and _orientation(L[-2], L[-1], p_i) >= 0:
|
||||
L.pop()
|
||||
U.append(p_i)
|
||||
L.append(p_i)
|
||||
U.reverse()
|
||||
convexHull = tuple(L + U[1:-1])
|
||||
|
||||
if len(convexHull) == 2:
|
||||
s = Segment(convexHull[0], convexHull[1])
|
||||
return s if polygon else (s, None)
|
||||
if polygon:
|
||||
return Polygon(*convexHull)
|
||||
else:
|
||||
U.reverse()
|
||||
return (U, L)
|
||||
|
||||
def farthest_points(*args):
|
||||
"""Return the subset of points from a set of points that were
|
||||
the furthest apart from each other in the 2D plane.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
args
|
||||
A collection of Points on 2D plane.
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
This can only be performed on a set of points whose coordinates can
|
||||
be ordered on the number line. If there are no ties then a single
|
||||
pair of Points will be in the set.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.geometry import farthest_points, Triangle
|
||||
>>> Triangle(sss=(3, 4, 5)).args
|
||||
(Point2D(0, 0), Point2D(3, 0), Point2D(3, 4))
|
||||
>>> farthest_points(*_)
|
||||
{(Point2D(0, 0), Point2D(3, 4))}
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://code.activestate.com/recipes/117225-convex-hull-and-diameter-of-2d-point-sets/
|
||||
|
||||
.. [2] Rotating Callipers Technique
|
||||
https://en.wikipedia.org/wiki/Rotating_calipers
|
||||
|
||||
"""
|
||||
|
||||
def rotatingCalipers(Points):
|
||||
U, L = convex_hull(*Points, **{"polygon": False})
|
||||
|
||||
if L is None:
|
||||
if isinstance(U, Point):
|
||||
raise ValueError('At least two distinct points must be given.')
|
||||
yield U.args
|
||||
else:
|
||||
i = 0
|
||||
j = len(L) - 1
|
||||
while i < len(U) - 1 or j > 0:
|
||||
yield U[i], L[j]
|
||||
# if all the way through one side of hull, advance the other side
|
||||
if i == len(U) - 1:
|
||||
j -= 1
|
||||
elif j == 0:
|
||||
i += 1
|
||||
# still points left on both lists, compare slopes of next hull edges
|
||||
# being careful to avoid divide-by-zero in slope calculation
|
||||
elif (U[i+1].y - U[i].y) * (L[j].x - L[j-1].x) > \
|
||||
(L[j].y - L[j-1].y) * (U[i+1].x - U[i].x):
|
||||
i += 1
|
||||
else:
|
||||
j -= 1
|
||||
|
||||
p = [Point2D(i) for i in set(args)]
|
||||
|
||||
if not all(i.is_Rational for j in p for i in j.args):
|
||||
def hypot(x, y):
|
||||
arg = x*x + y*y
|
||||
if arg.is_Rational:
|
||||
return _sqrt(arg)
|
||||
return sqrt(arg)
|
||||
else:
|
||||
from math import hypot
|
||||
|
||||
rv = []
|
||||
diam = 0
|
||||
for pair in rotatingCalipers(args):
|
||||
h, q = _ordered_points(pair)
|
||||
d = hypot(h.x - q.x, h.y - q.y)
|
||||
if d > diam:
|
||||
rv = [(h, q)]
|
||||
elif d == diam:
|
||||
rv.append((h, q))
|
||||
else:
|
||||
continue
|
||||
diam = d
|
||||
|
||||
return set(rv)
|
||||
|
||||
|
||||
def idiff(eq, y, x, n=1):
|
||||
"""Return ``dy/dx`` assuming that ``eq == 0``.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
y : the dependent variable or a list of dependent variables (with y first)
|
||||
x : the variable that the derivative is being taken with respect to
|
||||
n : the order of the derivative (default is 1)
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.abc import x, y, a
|
||||
>>> from sympy.geometry.util import idiff
|
||||
|
||||
>>> circ = x**2 + y**2 - 4
|
||||
>>> idiff(circ, y, x)
|
||||
-x/y
|
||||
>>> idiff(circ, y, x, 2).simplify()
|
||||
(-x**2 - y**2)/y**3
|
||||
|
||||
Here, ``a`` is assumed to be independent of ``x``:
|
||||
|
||||
>>> idiff(x + a + y, y, x)
|
||||
-1
|
||||
|
||||
Now the x-dependence of ``a`` is made explicit by listing ``a`` after
|
||||
``y`` in a list.
|
||||
|
||||
>>> idiff(x + a + y, [y, a], x)
|
||||
-Derivative(a, x) - 1
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.core.function.Derivative: represents unevaluated derivatives
|
||||
sympy.core.function.diff: explicitly differentiates wrt symbols
|
||||
|
||||
"""
|
||||
if is_sequence(y):
|
||||
dep = set(y)
|
||||
y = y[0]
|
||||
elif isinstance(y, Symbol):
|
||||
dep = {y}
|
||||
elif isinstance(y, Function):
|
||||
pass
|
||||
else:
|
||||
raise ValueError("expecting x-dependent symbol(s) or function(s) but got: %s" % y)
|
||||
|
||||
f = {s: Function(s.name)(x) for s in eq.free_symbols
|
||||
if s != x and s in dep}
|
||||
|
||||
if isinstance(y, Symbol):
|
||||
dydx = Function(y.name)(x).diff(x)
|
||||
else:
|
||||
dydx = y.diff(x)
|
||||
|
||||
eq = eq.subs(f)
|
||||
derivs = {}
|
||||
for i in range(n):
|
||||
# equation will be linear in dydx, a*dydx + b, so dydx = -b/a
|
||||
deq = eq.diff(x)
|
||||
b = deq.xreplace({dydx: S.Zero})
|
||||
a = (deq - b).xreplace({dydx: S.One})
|
||||
yp = factor_terms(expand_mul(cancel((-b/a).subs(derivs)), deep=False))
|
||||
if i == n - 1:
|
||||
return yp.subs([(v, k) for k, v in f.items()])
|
||||
derivs[dydx] = yp
|
||||
eq = dydx - yp
|
||||
dydx = dydx.diff(x)
|
||||
|
||||
|
||||
def intersection(*entities, pairwise=False, **kwargs):
|
||||
"""The intersection of a collection of GeometryEntity instances.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
entities : sequence of GeometryEntity
|
||||
pairwise (keyword argument) : Can be either True or False
|
||||
|
||||
Returns
|
||||
=======
|
||||
intersection : list of GeometryEntity
|
||||
|
||||
Raises
|
||||
======
|
||||
NotImplementedError
|
||||
When unable to calculate intersection.
|
||||
|
||||
Notes
|
||||
=====
|
||||
The intersection of any geometrical entity with itself should return
|
||||
a list with one item: the entity in question.
|
||||
An intersection requires two or more entities. If only a single
|
||||
entity is given then the function will return an empty list.
|
||||
It is possible for `intersection` to miss intersections that one
|
||||
knows exists because the required quantities were not fully
|
||||
simplified internally.
|
||||
Reals should be converted to Rationals, e.g. Rational(str(real_num))
|
||||
or else failures due to floating point issues may result.
|
||||
|
||||
Case 1: When the keyword argument 'pairwise' is False (default value):
|
||||
In this case, the function returns a list of intersections common to
|
||||
all entities.
|
||||
|
||||
Case 2: When the keyword argument 'pairwise' is True:
|
||||
In this case, the functions returns a list intersections that occur
|
||||
between any pair of entities.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.geometry.entity.GeometryEntity.intersection
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Ray, Circle, intersection
|
||||
>>> c = Circle((0, 1), 1)
|
||||
>>> intersection(c, c.center)
|
||||
[]
|
||||
>>> right = Ray((0, 0), (1, 0))
|
||||
>>> up = Ray((0, 0), (0, 1))
|
||||
>>> intersection(c, right, up)
|
||||
[Point2D(0, 0)]
|
||||
>>> intersection(c, right, up, pairwise=True)
|
||||
[Point2D(0, 0), Point2D(0, 2)]
|
||||
>>> left = Ray((1, 0), (0, 0))
|
||||
>>> intersection(right, left)
|
||||
[Segment2D(Point2D(0, 0), Point2D(1, 0))]
|
||||
|
||||
"""
|
||||
if len(entities) <= 1:
|
||||
return []
|
||||
|
||||
entities = list(entities)
|
||||
prec = None
|
||||
for i, e in enumerate(entities):
|
||||
if not isinstance(e, GeometryEntity):
|
||||
# entities may be an immutable tuple
|
||||
e = Point(e)
|
||||
# convert to exact Rationals
|
||||
d = {}
|
||||
for f in e.atoms(Float):
|
||||
prec = f._prec if prec is None else min(f._prec, prec)
|
||||
d.setdefault(f, nsimplify(f, rational=True))
|
||||
entities[i] = e.xreplace(d)
|
||||
|
||||
if not pairwise:
|
||||
# find the intersection common to all objects
|
||||
res = entities[0].intersection(entities[1])
|
||||
for entity in entities[2:]:
|
||||
newres = []
|
||||
for x in res:
|
||||
newres.extend(x.intersection(entity))
|
||||
res = newres
|
||||
else:
|
||||
# find all pairwise intersections
|
||||
ans = []
|
||||
for j in range(len(entities)):
|
||||
for k in range(j + 1, len(entities)):
|
||||
ans.extend(intersection(entities[j], entities[k]))
|
||||
res = list(ordered(set(ans)))
|
||||
|
||||
# convert back to Floats
|
||||
if prec is not None:
|
||||
p = prec_to_dps(prec)
|
||||
res = [i.n(p) for i in res]
|
||||
return res
|
||||
Reference in New Issue
Block a user