207 lines
6.7 KiB
Python
207 lines
6.7 KiB
Python
|
from sympy.core import Basic, diff
|
||
|
from sympy.core.singleton import S
|
||
|
from sympy.core.sorting import default_sort_key
|
||
|
from sympy.matrices import Matrix
|
||
|
from sympy.integrals import Integral, integrate
|
||
|
from sympy.geometry.entity import GeometryEntity
|
||
|
from sympy.simplify.simplify import simplify
|
||
|
from sympy.utilities.iterables import topological_sort
|
||
|
from sympy.vector import (CoordSys3D, Vector, ParametricRegion,
|
||
|
parametric_region_list, ImplicitRegion)
|
||
|
from sympy.vector.operators import _get_coord_systems
|
||
|
|
||
|
|
||
|
class ParametricIntegral(Basic):
|
||
|
"""
|
||
|
Represents integral of a scalar or vector field
|
||
|
over a Parametric Region
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy import cos, sin, pi
|
||
|
>>> from sympy.vector import CoordSys3D, ParametricRegion, ParametricIntegral
|
||
|
>>> from sympy.abc import r, t, theta, phi
|
||
|
|
||
|
>>> C = CoordSys3D('C')
|
||
|
>>> curve = ParametricRegion((3*t - 2, t + 1), (t, 1, 2))
|
||
|
>>> ParametricIntegral(C.x, curve)
|
||
|
5*sqrt(10)/2
|
||
|
>>> length = ParametricIntegral(1, curve)
|
||
|
>>> length
|
||
|
sqrt(10)
|
||
|
>>> semisphere = ParametricRegion((2*sin(phi)*cos(theta), 2*sin(phi)*sin(theta), 2*cos(phi)),\
|
||
|
(theta, 0, 2*pi), (phi, 0, pi/2))
|
||
|
>>> ParametricIntegral(C.z, semisphere)
|
||
|
8*pi
|
||
|
|
||
|
>>> ParametricIntegral(C.j + C.k, ParametricRegion((r*cos(theta), r*sin(theta)), r, theta))
|
||
|
0
|
||
|
|
||
|
"""
|
||
|
|
||
|
def __new__(cls, field, parametricregion):
|
||
|
|
||
|
coord_set = _get_coord_systems(field)
|
||
|
|
||
|
if len(coord_set) == 0:
|
||
|
coord_sys = CoordSys3D('C')
|
||
|
elif len(coord_set) > 1:
|
||
|
raise ValueError
|
||
|
else:
|
||
|
coord_sys = next(iter(coord_set))
|
||
|
|
||
|
if parametricregion.dimensions == 0:
|
||
|
return S.Zero
|
||
|
|
||
|
base_vectors = coord_sys.base_vectors()
|
||
|
base_scalars = coord_sys.base_scalars()
|
||
|
|
||
|
parametricfield = field
|
||
|
|
||
|
r = Vector.zero
|
||
|
for i in range(len(parametricregion.definition)):
|
||
|
r += base_vectors[i]*parametricregion.definition[i]
|
||
|
|
||
|
if len(coord_set) != 0:
|
||
|
for i in range(len(parametricregion.definition)):
|
||
|
parametricfield = parametricfield.subs(base_scalars[i], parametricregion.definition[i])
|
||
|
|
||
|
if parametricregion.dimensions == 1:
|
||
|
parameter = parametricregion.parameters[0]
|
||
|
|
||
|
r_diff = diff(r, parameter)
|
||
|
lower, upper = parametricregion.limits[parameter][0], parametricregion.limits[parameter][1]
|
||
|
|
||
|
if isinstance(parametricfield, Vector):
|
||
|
integrand = simplify(r_diff.dot(parametricfield))
|
||
|
else:
|
||
|
integrand = simplify(r_diff.magnitude()*parametricfield)
|
||
|
|
||
|
result = integrate(integrand, (parameter, lower, upper))
|
||
|
|
||
|
elif parametricregion.dimensions == 2:
|
||
|
u, v = cls._bounds_case(parametricregion.parameters, parametricregion.limits)
|
||
|
|
||
|
r_u = diff(r, u)
|
||
|
r_v = diff(r, v)
|
||
|
normal_vector = simplify(r_u.cross(r_v))
|
||
|
|
||
|
if isinstance(parametricfield, Vector):
|
||
|
integrand = parametricfield.dot(normal_vector)
|
||
|
else:
|
||
|
integrand = parametricfield*normal_vector.magnitude()
|
||
|
|
||
|
integrand = simplify(integrand)
|
||
|
|
||
|
lower_u, upper_u = parametricregion.limits[u][0], parametricregion.limits[u][1]
|
||
|
lower_v, upper_v = parametricregion.limits[v][0], parametricregion.limits[v][1]
|
||
|
|
||
|
result = integrate(integrand, (u, lower_u, upper_u), (v, lower_v, upper_v))
|
||
|
|
||
|
else:
|
||
|
variables = cls._bounds_case(parametricregion.parameters, parametricregion.limits)
|
||
|
coeff = Matrix(parametricregion.definition).jacobian(variables).det()
|
||
|
integrand = simplify(parametricfield*coeff)
|
||
|
|
||
|
l = [(var, parametricregion.limits[var][0], parametricregion.limits[var][1]) for var in variables]
|
||
|
result = integrate(integrand, *l)
|
||
|
|
||
|
if not isinstance(result, Integral):
|
||
|
return result
|
||
|
else:
|
||
|
return super().__new__(cls, field, parametricregion)
|
||
|
|
||
|
@classmethod
|
||
|
def _bounds_case(cls, parameters, limits):
|
||
|
|
||
|
V = list(limits.keys())
|
||
|
E = []
|
||
|
|
||
|
for p in V:
|
||
|
lower_p = limits[p][0]
|
||
|
upper_p = limits[p][1]
|
||
|
|
||
|
lower_p = lower_p.atoms()
|
||
|
upper_p = upper_p.atoms()
|
||
|
E.extend((p, q) for q in V if p != q and
|
||
|
(lower_p.issuperset({q}) or upper_p.issuperset({q})))
|
||
|
|
||
|
if not E:
|
||
|
return parameters
|
||
|
else:
|
||
|
return topological_sort((V, E), key=default_sort_key)
|
||
|
|
||
|
@property
|
||
|
def field(self):
|
||
|
return self.args[0]
|
||
|
|
||
|
@property
|
||
|
def parametricregion(self):
|
||
|
return self.args[1]
|
||
|
|
||
|
|
||
|
def vector_integrate(field, *region):
|
||
|
"""
|
||
|
Compute the integral of a vector/scalar field
|
||
|
over a a region or a set of parameters.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
>>> from sympy.vector import CoordSys3D, ParametricRegion, vector_integrate
|
||
|
>>> from sympy.abc import x, y, t
|
||
|
>>> C = CoordSys3D('C')
|
||
|
|
||
|
>>> region = ParametricRegion((t, t**2), (t, 1, 5))
|
||
|
>>> vector_integrate(C.x*C.i, region)
|
||
|
12
|
||
|
|
||
|
Integrals over some objects of geometry module can also be calculated.
|
||
|
|
||
|
>>> from sympy.geometry import Point, Circle, Triangle
|
||
|
>>> c = Circle(Point(0, 2), 5)
|
||
|
>>> vector_integrate(C.x**2 + C.y**2, c)
|
||
|
290*pi
|
||
|
>>> triangle = Triangle(Point(-2, 3), Point(2, 3), Point(0, 5))
|
||
|
>>> vector_integrate(3*C.x**2*C.y*C.i + C.j, triangle)
|
||
|
-8
|
||
|
|
||
|
Integrals over some simple implicit regions can be computed. But in most cases,
|
||
|
it takes too long to compute over them. This is due to the expressions of parametric
|
||
|
representation becoming large.
|
||
|
|
||
|
>>> from sympy.vector import ImplicitRegion
|
||
|
>>> c2 = ImplicitRegion((x, y), (x - 2)**2 + (y - 1)**2 - 9)
|
||
|
>>> vector_integrate(1, c2)
|
||
|
6*pi
|
||
|
|
||
|
Integral of fields with respect to base scalars:
|
||
|
|
||
|
>>> vector_integrate(12*C.y**3, (C.y, 1, 3))
|
||
|
240
|
||
|
>>> vector_integrate(C.x**2*C.z, C.x)
|
||
|
C.x**3*C.z/3
|
||
|
>>> vector_integrate(C.x*C.i - C.y*C.k, C.x)
|
||
|
(Integral(C.x, C.x))*C.i + (Integral(-C.y, C.x))*C.k
|
||
|
>>> _.doit()
|
||
|
C.x**2/2*C.i + (-C.x*C.y)*C.k
|
||
|
|
||
|
"""
|
||
|
if len(region) == 1:
|
||
|
if isinstance(region[0], ParametricRegion):
|
||
|
return ParametricIntegral(field, region[0])
|
||
|
|
||
|
if isinstance(region[0], ImplicitRegion):
|
||
|
region = parametric_region_list(region[0])[0]
|
||
|
return vector_integrate(field, region)
|
||
|
|
||
|
if isinstance(region[0], GeometryEntity):
|
||
|
regions_list = parametric_region_list(region[0])
|
||
|
|
||
|
result = 0
|
||
|
for reg in regions_list:
|
||
|
result += vector_integrate(field, reg)
|
||
|
return result
|
||
|
|
||
|
return integrate(field, *region)
|