You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

190 lines
5.8 KiB

from functools import singledispatch
from sympy.core.numbers import pi
from sympy.functions.elementary.trigonometric import tan
from sympy.simplify import trigsimp
from sympy.core import Basic, Tuple
from sympy.core.symbol import _symbol
from sympy.solvers import solve
from sympy.geometry import Point, Segment, Curve, Ellipse, Polygon
from sympy.vector import ImplicitRegion
class ParametricRegion(Basic):
"""
Represents a parametric region in space.
Examples
========
>>> from sympy import cos, sin, pi
>>> from sympy.abc import r, theta, t, a, b, x, y
>>> from sympy.vector import ParametricRegion
>>> ParametricRegion((t, t**2), (t, -1, 2))
ParametricRegion((t, t**2), (t, -1, 2))
>>> ParametricRegion((x, y), (x, 3, 4), (y, 5, 6))
ParametricRegion((x, y), (x, 3, 4), (y, 5, 6))
>>> ParametricRegion((r*cos(theta), r*sin(theta)), (r, -2, 2), (theta, 0, pi))
ParametricRegion((r*cos(theta), r*sin(theta)), (r, -2, 2), (theta, 0, pi))
>>> ParametricRegion((a*cos(t), b*sin(t)), t)
ParametricRegion((a*cos(t), b*sin(t)), t)
>>> circle = ParametricRegion((r*cos(theta), r*sin(theta)), r, (theta, 0, pi))
>>> circle.parameters
(r, theta)
>>> circle.definition
(r*cos(theta), r*sin(theta))
>>> circle.limits
{theta: (0, pi)}
Dimension of a parametric region determines whether a region is a curve, surface
or volume region. It does not represent its dimensions in space.
>>> circle.dimensions
1
Parameters
==========
definition : tuple to define base scalars in terms of parameters.
bounds : Parameter or a tuple of length 3 to define parameter and corresponding lower and upper bound.
"""
def __new__(cls, definition, *bounds):
parameters = ()
limits = {}
if not isinstance(bounds, Tuple):
bounds = Tuple(*bounds)
for bound in bounds:
if isinstance(bound, (tuple, Tuple)):
if len(bound) != 3:
raise ValueError("Tuple should be in the form (parameter, lowerbound, upperbound)")
parameters += (bound[0],)
limits[bound[0]] = (bound[1], bound[2])
else:
parameters += (bound,)
if not isinstance(definition, (tuple, Tuple)):
definition = (definition,)
obj = super().__new__(cls, Tuple(*definition), *bounds)
obj._parameters = parameters
obj._limits = limits
return obj
@property
def definition(self):
return self.args[0]
@property
def limits(self):
return self._limits
@property
def parameters(self):
return self._parameters
@property
def dimensions(self):
return len(self.limits)
@singledispatch
def parametric_region_list(reg):
"""
Returns a list of ParametricRegion objects representing the geometric region.
Examples
========
>>> from sympy.abc import t
>>> from sympy.vector import parametric_region_list
>>> from sympy.geometry import Point, Curve, Ellipse, Segment, Polygon
>>> p = Point(2, 5)
>>> parametric_region_list(p)
[ParametricRegion((2, 5))]
>>> c = Curve((t**3, 4*t), (t, -3, 4))
>>> parametric_region_list(c)
[ParametricRegion((t**3, 4*t), (t, -3, 4))]
>>> e = Ellipse(Point(1, 3), 2, 3)
>>> parametric_region_list(e)
[ParametricRegion((2*cos(t) + 1, 3*sin(t) + 3), (t, 0, 2*pi))]
>>> s = Segment(Point(1, 3), Point(2, 6))
>>> parametric_region_list(s)
[ParametricRegion((t + 1, 3*t + 3), (t, 0, 1))]
>>> p1, p2, p3, p4 = [(0, 1), (2, -3), (5, 3), (-2, 3)]
>>> poly = Polygon(p1, p2, p3, p4)
>>> parametric_region_list(poly)
[ParametricRegion((2*t, 1 - 4*t), (t, 0, 1)), ParametricRegion((3*t + 2, 6*t - 3), (t, 0, 1)),\
ParametricRegion((5 - 7*t, 3), (t, 0, 1)), ParametricRegion((2*t - 2, 3 - 2*t), (t, 0, 1))]
"""
raise ValueError("SymPy cannot determine parametric representation of the region.")
@parametric_region_list.register(Point)
def _(obj):
return [ParametricRegion(obj.args)]
@parametric_region_list.register(Curve) # type: ignore
def _(obj):
definition = obj.arbitrary_point(obj.parameter).args
bounds = obj.limits
return [ParametricRegion(definition, bounds)]
@parametric_region_list.register(Ellipse) # type: ignore
def _(obj, parameter='t'):
definition = obj.arbitrary_point(parameter).args
t = _symbol(parameter, real=True)
bounds = (t, 0, 2*pi)
return [ParametricRegion(definition, bounds)]
@parametric_region_list.register(Segment) # type: ignore
def _(obj, parameter='t'):
t = _symbol(parameter, real=True)
definition = obj.arbitrary_point(t).args
for i in range(0, 3):
lower_bound = solve(definition[i] - obj.points[0].args[i], t)
upper_bound = solve(definition[i] - obj.points[1].args[i], t)
if len(lower_bound) == 1 and len(upper_bound) == 1:
bounds = t, lower_bound[0], upper_bound[0]
break
definition_tuple = obj.arbitrary_point(parameter).args
return [ParametricRegion(definition_tuple, bounds)]
@parametric_region_list.register(Polygon) # type: ignore
def _(obj, parameter='t'):
l = [parametric_region_list(side, parameter)[0] for side in obj.sides]
return l
@parametric_region_list.register(ImplicitRegion) # type: ignore
def _(obj, parameters=('t', 's')):
definition = obj.rational_parametrization(parameters)
bounds = []
for i in range(len(obj.variables) - 1):
# Each parameter is replaced by its tangent to simplify intergation
parameter = _symbol(parameters[i], real=True)
definition = [trigsimp(elem.subs(parameter, tan(parameter/2))) for elem in definition]
bounds.append((parameter, 0, 2*pi),)
definition = Tuple(*definition)
return [ParametricRegion(definition, *bounds)]