Spaces:
Sleeping
Sleeping
""" | |
Module to implement integration of uni/bivariate polynomials over | |
2D Polytopes and uni/bi/trivariate polynomials over 3D Polytopes. | |
Uses evaluation techniques as described in Chin et al. (2015) [1]. | |
References | |
=========== | |
.. [1] Chin, Eric B., Jean B. Lasserre, and N. Sukumar. "Numerical integration | |
of homogeneous functions on convex and nonconvex polygons and polyhedra." | |
Computational Mechanics 56.6 (2015): 967-981 | |
PDF link : http://dilbert.engr.ucdavis.edu/~suku/quadrature/cls-integration.pdf | |
""" | |
from functools import cmp_to_key | |
from sympy.abc import x, y, z | |
from sympy.core import S, diff, Expr, Symbol | |
from sympy.core.sympify import _sympify | |
from sympy.geometry import Segment2D, Polygon, Point, Point2D | |
from sympy.polys.polytools import LC, gcd_list, degree_list, Poly | |
from sympy.simplify.simplify import nsimplify | |
def polytope_integrate(poly, expr=None, *, clockwise=False, max_degree=None): | |
"""Integrates polynomials over 2/3-Polytopes. | |
Explanation | |
=========== | |
This function accepts the polytope in ``poly`` and the function in ``expr`` | |
(uni/bi/trivariate polynomials are implemented) and returns | |
the exact integral of ``expr`` over ``poly``. | |
Parameters | |
========== | |
poly : The input Polygon. | |
expr : The input polynomial. | |
clockwise : Binary value to sort input points of 2-Polytope clockwise.(Optional) | |
max_degree : The maximum degree of any monomial of the input polynomial.(Optional) | |
Examples | |
======== | |
>>> from sympy.abc import x, y | |
>>> from sympy import Point, Polygon | |
>>> from sympy.integrals.intpoly import polytope_integrate | |
>>> polygon = Polygon(Point(0, 0), Point(0, 1), Point(1, 1), Point(1, 0)) | |
>>> polys = [1, x, y, x*y, x**2*y, x*y**2] | |
>>> expr = x*y | |
>>> polytope_integrate(polygon, expr) | |
1/4 | |
>>> polytope_integrate(polygon, polys, max_degree=3) | |
{1: 1, x: 1/2, y: 1/2, x*y: 1/4, x*y**2: 1/6, x**2*y: 1/6} | |
""" | |
if clockwise: | |
if isinstance(poly, Polygon): | |
poly = Polygon(*point_sort(poly.vertices), evaluate=False) | |
else: | |
raise TypeError("clockwise=True works for only 2-Polytope" | |
"V-representation input") | |
if isinstance(poly, Polygon): | |
# For Vertex Representation(2D case) | |
hp_params = hyperplane_parameters(poly) | |
facets = poly.sides | |
elif len(poly[0]) == 2: | |
# For Hyperplane Representation(2D case) | |
plen = len(poly) | |
if len(poly[0][0]) == 2: | |
intersections = [intersection(poly[(i - 1) % plen], poly[i], | |
"plane2D") | |
for i in range(0, plen)] | |
hp_params = poly | |
lints = len(intersections) | |
facets = [Segment2D(intersections[i], | |
intersections[(i + 1) % lints]) | |
for i in range(lints)] | |
else: | |
raise NotImplementedError("Integration for H-representation 3D" | |
"case not implemented yet.") | |
else: | |
# For Vertex Representation(3D case) | |
vertices = poly[0] | |
facets = poly[1:] | |
hp_params = hyperplane_parameters(facets, vertices) | |
if max_degree is None: | |
if expr is None: | |
raise TypeError('Input expression must be a valid SymPy expression') | |
return main_integrate3d(expr, facets, vertices, hp_params) | |
if max_degree is not None: | |
result = {} | |
if expr is not None: | |
f_expr = [] | |
for e in expr: | |
_ = decompose(e) | |
if len(_) == 1 and not _.popitem()[0]: | |
f_expr.append(e) | |
elif Poly(e).total_degree() <= max_degree: | |
f_expr.append(e) | |
expr = f_expr | |
if not isinstance(expr, list) and expr is not None: | |
raise TypeError('Input polynomials must be list of expressions') | |
if len(hp_params[0][0]) == 3: | |
result_dict = main_integrate3d(0, facets, vertices, hp_params, | |
max_degree) | |
else: | |
result_dict = main_integrate(0, facets, hp_params, max_degree) | |
if expr is None: | |
return result_dict | |
for poly in expr: | |
poly = _sympify(poly) | |
if poly not in result: | |
if poly.is_zero: | |
result[S.Zero] = S.Zero | |
continue | |
integral_value = S.Zero | |
monoms = decompose(poly, separate=True) | |
for monom in monoms: | |
monom = nsimplify(monom) | |
coeff, m = strip(monom) | |
integral_value += result_dict[m] * coeff | |
result[poly] = integral_value | |
return result | |
if expr is None: | |
raise TypeError('Input expression must be a valid SymPy expression') | |
return main_integrate(expr, facets, hp_params) | |
def strip(monom): | |
if monom.is_zero: | |
return S.Zero, S.Zero | |
elif monom.is_number: | |
return monom, S.One | |
else: | |
coeff = LC(monom) | |
return coeff, monom / coeff | |
def _polynomial_integrate(polynomials, facets, hp_params): | |
dims = (x, y) | |
dim_length = len(dims) | |
integral_value = S.Zero | |
for deg in polynomials: | |
poly_contribute = S.Zero | |
facet_count = 0 | |
for hp in hp_params: | |
value_over_boundary = integration_reduction(facets, | |
facet_count, | |
hp[0], hp[1], | |
polynomials[deg], | |
dims, deg) | |
poly_contribute += value_over_boundary * (hp[1] / norm(hp[0])) | |
facet_count += 1 | |
poly_contribute /= (dim_length + deg) | |
integral_value += poly_contribute | |
return integral_value | |
def main_integrate3d(expr, facets, vertices, hp_params, max_degree=None): | |
"""Function to translate the problem of integrating uni/bi/tri-variate | |
polynomials over a 3-Polytope to integrating over its faces. | |
This is done using Generalized Stokes' Theorem and Euler's Theorem. | |
Parameters | |
========== | |
expr : | |
The input polynomial. | |
facets : | |
Faces of the 3-Polytope(expressed as indices of `vertices`). | |
vertices : | |
Vertices that constitute the Polytope. | |
hp_params : | |
Hyperplane Parameters of the facets. | |
max_degree : optional | |
Max degree of constituent monomial in given list of polynomial. | |
Examples | |
======== | |
>>> from sympy.integrals.intpoly import main_integrate3d, \ | |
hyperplane_parameters | |
>>> cube = [[(0, 0, 0), (0, 0, 5), (0, 5, 0), (0, 5, 5), (5, 0, 0),\ | |
(5, 0, 5), (5, 5, 0), (5, 5, 5)],\ | |
[2, 6, 7, 3], [3, 7, 5, 1], [7, 6, 4, 5], [1, 5, 4, 0],\ | |
[3, 1, 0, 2], [0, 4, 6, 2]] | |
>>> vertices = cube[0] | |
>>> faces = cube[1:] | |
>>> hp_params = hyperplane_parameters(faces, vertices) | |
>>> main_integrate3d(1, faces, vertices, hp_params) | |
-125 | |
""" | |
result = {} | |
dims = (x, y, z) | |
dim_length = len(dims) | |
if max_degree: | |
grad_terms = gradient_terms(max_degree, 3) | |
flat_list = [term for z_terms in grad_terms | |
for x_term in z_terms | |
for term in x_term] | |
for term in flat_list: | |
result[term[0]] = 0 | |
for facet_count, hp in enumerate(hp_params): | |
a, b = hp[0], hp[1] | |
x0 = vertices[facets[facet_count][0]] | |
for i, monom in enumerate(flat_list): | |
# Every monomial is a tuple : | |
# (term, x_degree, y_degree, z_degree, value over boundary) | |
expr, x_d, y_d, z_d, z_index, y_index, x_index, _ = monom | |
degree = x_d + y_d + z_d | |
if b.is_zero: | |
value_over_face = S.Zero | |
else: | |
value_over_face = \ | |
integration_reduction_dynamic(facets, facet_count, a, | |
b, expr, degree, dims, | |
x_index, y_index, | |
z_index, x0, grad_terms, | |
i, vertices, hp) | |
monom[7] = value_over_face | |
result[expr] += value_over_face * \ | |
(b / norm(a)) / (dim_length + x_d + y_d + z_d) | |
return result | |
else: | |
integral_value = S.Zero | |
polynomials = decompose(expr) | |
for deg in polynomials: | |
poly_contribute = S.Zero | |
facet_count = 0 | |
for i, facet in enumerate(facets): | |
hp = hp_params[i] | |
if hp[1].is_zero: | |
continue | |
pi = polygon_integrate(facet, hp, i, facets, vertices, expr, deg) | |
poly_contribute += pi *\ | |
(hp[1] / norm(tuple(hp[0]))) | |
facet_count += 1 | |
poly_contribute /= (dim_length + deg) | |
integral_value += poly_contribute | |
return integral_value | |
def main_integrate(expr, facets, hp_params, max_degree=None): | |
"""Function to translate the problem of integrating univariate/bivariate | |
polynomials over a 2-Polytope to integrating over its boundary facets. | |
This is done using Generalized Stokes's Theorem and Euler's Theorem. | |
Parameters | |
========== | |
expr : | |
The input polynomial. | |
facets : | |
Facets(Line Segments) of the 2-Polytope. | |
hp_params : | |
Hyperplane Parameters of the facets. | |
max_degree : optional | |
The maximum degree of any monomial of the input polynomial. | |
>>> from sympy.abc import x, y | |
>>> from sympy.integrals.intpoly import main_integrate,\ | |
hyperplane_parameters | |
>>> from sympy import Point, Polygon | |
>>> triangle = Polygon(Point(0, 3), Point(5, 3), Point(1, 1)) | |
>>> facets = triangle.sides | |
>>> hp_params = hyperplane_parameters(triangle) | |
>>> main_integrate(x**2 + y**2, facets, hp_params) | |
325/6 | |
""" | |
dims = (x, y) | |
dim_length = len(dims) | |
result = {} | |
if max_degree: | |
grad_terms = [[0, 0, 0, 0]] + gradient_terms(max_degree) | |
for facet_count, hp in enumerate(hp_params): | |
a, b = hp[0], hp[1] | |
x0 = facets[facet_count].points[0] | |
for i, monom in enumerate(grad_terms): | |
# Every monomial is a tuple : | |
# (term, x_degree, y_degree, value over boundary) | |
m, x_d, y_d, _ = monom | |
value = result.get(m, None) | |
degree = S.Zero | |
if b.is_zero: | |
value_over_boundary = S.Zero | |
else: | |
degree = x_d + y_d | |
value_over_boundary = \ | |
integration_reduction_dynamic(facets, facet_count, a, | |
b, m, degree, dims, x_d, | |
y_d, max_degree, x0, | |
grad_terms, i) | |
monom[3] = value_over_boundary | |
if value is not None: | |
result[m] += value_over_boundary * \ | |
(b / norm(a)) / (dim_length + degree) | |
else: | |
result[m] = value_over_boundary * \ | |
(b / norm(a)) / (dim_length + degree) | |
return result | |
else: | |
if not isinstance(expr, list): | |
polynomials = decompose(expr) | |
return _polynomial_integrate(polynomials, facets, hp_params) | |
else: | |
return {e: _polynomial_integrate(decompose(e), facets, hp_params) for e in expr} | |
def polygon_integrate(facet, hp_param, index, facets, vertices, expr, degree): | |
"""Helper function to integrate the input uni/bi/trivariate polynomial | |
over a certain face of the 3-Polytope. | |
Parameters | |
========== | |
facet : | |
Particular face of the 3-Polytope over which ``expr`` is integrated. | |
index : | |
The index of ``facet`` in ``facets``. | |
facets : | |
Faces of the 3-Polytope(expressed as indices of `vertices`). | |
vertices : | |
Vertices that constitute the facet. | |
expr : | |
The input polynomial. | |
degree : | |
Degree of ``expr``. | |
Examples | |
======== | |
>>> from sympy.integrals.intpoly import polygon_integrate | |
>>> cube = [[(0, 0, 0), (0, 0, 5), (0, 5, 0), (0, 5, 5), (5, 0, 0),\ | |
(5, 0, 5), (5, 5, 0), (5, 5, 5)],\ | |
[2, 6, 7, 3], [3, 7, 5, 1], [7, 6, 4, 5], [1, 5, 4, 0],\ | |
[3, 1, 0, 2], [0, 4, 6, 2]] | |
>>> facet = cube[1] | |
>>> facets = cube[1:] | |
>>> vertices = cube[0] | |
>>> polygon_integrate(facet, [(0, 1, 0), 5], 0, facets, vertices, 1, 0) | |
-25 | |
""" | |
expr = S(expr) | |
if expr.is_zero: | |
return S.Zero | |
result = S.Zero | |
x0 = vertices[facet[0]] | |
facet_len = len(facet) | |
for i, fac in enumerate(facet): | |
side = (vertices[fac], vertices[facet[(i + 1) % facet_len]]) | |
result += distance_to_side(x0, side, hp_param[0]) *\ | |
lineseg_integrate(facet, i, side, expr, degree) | |
if not expr.is_number: | |
expr = diff(expr, x) * x0[0] + diff(expr, y) * x0[1] +\ | |
diff(expr, z) * x0[2] | |
result += polygon_integrate(facet, hp_param, index, facets, vertices, | |
expr, degree - 1) | |
result /= (degree + 2) | |
return result | |
def distance_to_side(point, line_seg, A): | |
"""Helper function to compute the signed distance between given 3D point | |
and a line segment. | |
Parameters | |
========== | |
point : 3D Point | |
line_seg : Line Segment | |
Examples | |
======== | |
>>> from sympy.integrals.intpoly import distance_to_side | |
>>> point = (0, 0, 0) | |
>>> distance_to_side(point, [(0, 0, 1), (0, 1, 0)], (1, 0, 0)) | |
-sqrt(2)/2 | |
""" | |
x1, x2 = line_seg | |
rev_normal = [-1 * S(i)/norm(A) for i in A] | |
vector = [x2[i] - x1[i] for i in range(0, 3)] | |
vector = [vector[i]/norm(vector) for i in range(0, 3)] | |
n_side = cross_product((0, 0, 0), rev_normal, vector) | |
vectorx0 = [line_seg[0][i] - point[i] for i in range(0, 3)] | |
dot_product = sum(vectorx0[i] * n_side[i] for i in range(0, 3)) | |
return dot_product | |
def lineseg_integrate(polygon, index, line_seg, expr, degree): | |
"""Helper function to compute the line integral of ``expr`` over ``line_seg``. | |
Parameters | |
=========== | |
polygon : | |
Face of a 3-Polytope. | |
index : | |
Index of line_seg in polygon. | |
line_seg : | |
Line Segment. | |
Examples | |
======== | |
>>> from sympy.integrals.intpoly import lineseg_integrate | |
>>> polygon = [(0, 5, 0), (5, 5, 0), (5, 5, 5), (0, 5, 5)] | |
>>> line_seg = [(0, 5, 0), (5, 5, 0)] | |
>>> lineseg_integrate(polygon, 0, line_seg, 1, 0) | |
5 | |
""" | |
expr = _sympify(expr) | |
if expr.is_zero: | |
return S.Zero | |
result = S.Zero | |
x0 = line_seg[0] | |
distance = norm(tuple([line_seg[1][i] - line_seg[0][i] for i in | |
range(3)])) | |
if isinstance(expr, Expr): | |
expr_dict = {x: line_seg[1][0], | |
y: line_seg[1][1], | |
z: line_seg[1][2]} | |
result += distance * expr.subs(expr_dict) | |
else: | |
result += distance * expr | |
expr = diff(expr, x) * x0[0] + diff(expr, y) * x0[1] +\ | |
diff(expr, z) * x0[2] | |
result += lineseg_integrate(polygon, index, line_seg, expr, degree - 1) | |
result /= (degree + 1) | |
return result | |
def integration_reduction(facets, index, a, b, expr, dims, degree): | |
"""Helper method for main_integrate. Returns the value of the input | |
expression evaluated over the polytope facet referenced by a given index. | |
Parameters | |
=========== | |
facets : | |
List of facets of the polytope. | |
index : | |
Index referencing the facet to integrate the expression over. | |
a : | |
Hyperplane parameter denoting direction. | |
b : | |
Hyperplane parameter denoting distance. | |
expr : | |
The expression to integrate over the facet. | |
dims : | |
List of symbols denoting axes. | |
degree : | |
Degree of the homogeneous polynomial. | |
Examples | |
======== | |
>>> from sympy.abc import x, y | |
>>> from sympy.integrals.intpoly import integration_reduction,\ | |
hyperplane_parameters | |
>>> from sympy import Point, Polygon | |
>>> triangle = Polygon(Point(0, 3), Point(5, 3), Point(1, 1)) | |
>>> facets = triangle.sides | |
>>> a, b = hyperplane_parameters(triangle)[0] | |
>>> integration_reduction(facets, 0, a, b, 1, (x, y), 0) | |
5 | |
""" | |
expr = _sympify(expr) | |
if expr.is_zero: | |
return expr | |
value = S.Zero | |
x0 = facets[index].points[0] | |
m = len(facets) | |
gens = (x, y) | |
inner_product = diff(expr, gens[0]) * x0[0] + diff(expr, gens[1]) * x0[1] | |
if inner_product != 0: | |
value += integration_reduction(facets, index, a, b, | |
inner_product, dims, degree - 1) | |
value += left_integral2D(m, index, facets, x0, expr, gens) | |
return value/(len(dims) + degree - 1) | |
def left_integral2D(m, index, facets, x0, expr, gens): | |
"""Computes the left integral of Eq 10 in Chin et al. | |
For the 2D case, the integral is just an evaluation of the polynomial | |
at the intersection of two facets which is multiplied by the distance | |
between the first point of facet and that intersection. | |
Parameters | |
========== | |
m : | |
No. of hyperplanes. | |
index : | |
Index of facet to find intersections with. | |
facets : | |
List of facets(Line Segments in 2D case). | |
x0 : | |
First point on facet referenced by index. | |
expr : | |
Input polynomial | |
gens : | |
Generators which generate the polynomial | |
Examples | |
======== | |
>>> from sympy.abc import x, y | |
>>> from sympy.integrals.intpoly import left_integral2D | |
>>> from sympy import Point, Polygon | |
>>> triangle = Polygon(Point(0, 3), Point(5, 3), Point(1, 1)) | |
>>> facets = triangle.sides | |
>>> left_integral2D(3, 0, facets, facets[0].points[0], 1, (x, y)) | |
5 | |
""" | |
value = S.Zero | |
for j in range(m): | |
intersect = () | |
if j in ((index - 1) % m, (index + 1) % m): | |
intersect = intersection(facets[index], facets[j], "segment2D") | |
if intersect: | |
distance_origin = norm(tuple(map(lambda x, y: x - y, | |
intersect, x0))) | |
if is_vertex(intersect): | |
if isinstance(expr, Expr): | |
if len(gens) == 3: | |
expr_dict = {gens[0]: intersect[0], | |
gens[1]: intersect[1], | |
gens[2]: intersect[2]} | |
else: | |
expr_dict = {gens[0]: intersect[0], | |
gens[1]: intersect[1]} | |
value += distance_origin * expr.subs(expr_dict) | |
else: | |
value += distance_origin * expr | |
return value | |
def integration_reduction_dynamic(facets, index, a, b, expr, degree, dims, | |
x_index, y_index, max_index, x0, | |
monomial_values, monom_index, vertices=None, | |
hp_param=None): | |
"""The same integration_reduction function which uses a dynamic | |
programming approach to compute terms by using the values of the integral | |
of previously computed terms. | |
Parameters | |
========== | |
facets : | |
Facets of the Polytope. | |
index : | |
Index of facet to find intersections with.(Used in left_integral()). | |
a, b : | |
Hyperplane parameters. | |
expr : | |
Input monomial. | |
degree : | |
Total degree of ``expr``. | |
dims : | |
Tuple denoting axes variables. | |
x_index : | |
Exponent of 'x' in ``expr``. | |
y_index : | |
Exponent of 'y' in ``expr``. | |
max_index : | |
Maximum exponent of any monomial in ``monomial_values``. | |
x0 : | |
First point on ``facets[index]``. | |
monomial_values : | |
List of monomial values constituting the polynomial. | |
monom_index : | |
Index of monomial whose integration is being found. | |
vertices : optional | |
Coordinates of vertices constituting the 3-Polytope. | |
hp_param : optional | |
Hyperplane Parameter of the face of the facets[index]. | |
Examples | |
======== | |
>>> from sympy.abc import x, y | |
>>> from sympy.integrals.intpoly import (integration_reduction_dynamic, \ | |
hyperplane_parameters) | |
>>> from sympy import Point, Polygon | |
>>> triangle = Polygon(Point(0, 3), Point(5, 3), Point(1, 1)) | |
>>> facets = triangle.sides | |
>>> a, b = hyperplane_parameters(triangle)[0] | |
>>> x0 = facets[0].points[0] | |
>>> monomial_values = [[0, 0, 0, 0], [1, 0, 0, 5],\ | |
[y, 0, 1, 15], [x, 1, 0, None]] | |
>>> integration_reduction_dynamic(facets, 0, a, b, x, 1, (x, y), 1, 0, 1,\ | |
x0, monomial_values, 3) | |
25/2 | |
""" | |
value = S.Zero | |
m = len(facets) | |
if expr == S.Zero: | |
return expr | |
if len(dims) == 2: | |
if not expr.is_number: | |
_, x_degree, y_degree, _ = monomial_values[monom_index] | |
x_index = monom_index - max_index + \ | |
x_index - 2 if x_degree > 0 else 0 | |
y_index = monom_index - 1 if y_degree > 0 else 0 | |
x_value, y_value =\ | |
monomial_values[x_index][3], monomial_values[y_index][3] | |
value += x_degree * x_value * x0[0] + y_degree * y_value * x0[1] | |
value += left_integral2D(m, index, facets, x0, expr, dims) | |
else: | |
# For 3D use case the max_index contains the z_degree of the term | |
z_index = max_index | |
if not expr.is_number: | |
x_degree, y_degree, z_degree = y_index,\ | |
z_index - x_index - y_index, x_index | |
x_value = monomial_values[z_index - 1][y_index - 1][x_index][7]\ | |
if x_degree > 0 else 0 | |
y_value = monomial_values[z_index - 1][y_index][x_index][7]\ | |
if y_degree > 0 else 0 | |
z_value = monomial_values[z_index - 1][y_index][x_index - 1][7]\ | |
if z_degree > 0 else 0 | |
value += x_degree * x_value * x0[0] + y_degree * y_value * x0[1] \ | |
+ z_degree * z_value * x0[2] | |
value += left_integral3D(facets, index, expr, | |
vertices, hp_param, degree) | |
return value / (len(dims) + degree - 1) | |
def left_integral3D(facets, index, expr, vertices, hp_param, degree): | |
"""Computes the left integral of Eq 10 in Chin et al. | |
Explanation | |
=========== | |
For the 3D case, this is the sum of the integral values over constituting | |
line segments of the face (which is accessed by facets[index]) multiplied | |
by the distance between the first point of facet and that line segment. | |
Parameters | |
========== | |
facets : | |
List of faces of the 3-Polytope. | |
index : | |
Index of face over which integral is to be calculated. | |
expr : | |
Input polynomial. | |
vertices : | |
List of vertices that constitute the 3-Polytope. | |
hp_param : | |
The hyperplane parameters of the face. | |
degree : | |
Degree of the ``expr``. | |
Examples | |
======== | |
>>> from sympy.integrals.intpoly import left_integral3D | |
>>> cube = [[(0, 0, 0), (0, 0, 5), (0, 5, 0), (0, 5, 5), (5, 0, 0),\ | |
(5, 0, 5), (5, 5, 0), (5, 5, 5)],\ | |
[2, 6, 7, 3], [3, 7, 5, 1], [7, 6, 4, 5], [1, 5, 4, 0],\ | |
[3, 1, 0, 2], [0, 4, 6, 2]] | |
>>> facets = cube[1:] | |
>>> vertices = cube[0] | |
>>> left_integral3D(facets, 3, 1, vertices, ([0, -1, 0], -5), 0) | |
-50 | |
""" | |
value = S.Zero | |
facet = facets[index] | |
x0 = vertices[facet[0]] | |
facet_len = len(facet) | |
for i, fac in enumerate(facet): | |
side = (vertices[fac], vertices[facet[(i + 1) % facet_len]]) | |
value += distance_to_side(x0, side, hp_param[0]) * \ | |
lineseg_integrate(facet, i, side, expr, degree) | |
return value | |
def gradient_terms(binomial_power=0, no_of_gens=2): | |
"""Returns a list of all the possible monomials between | |
0 and y**binomial_power for 2D case and z**binomial_power | |
for 3D case. | |
Parameters | |
========== | |
binomial_power : | |
Power upto which terms are generated. | |
no_of_gens : | |
Denotes whether terms are being generated for 2D or 3D case. | |
Examples | |
======== | |
>>> from sympy.integrals.intpoly import gradient_terms | |
>>> gradient_terms(2) | |
[[1, 0, 0, 0], [y, 0, 1, 0], [y**2, 0, 2, 0], [x, 1, 0, 0], | |
[x*y, 1, 1, 0], [x**2, 2, 0, 0]] | |
>>> gradient_terms(2, 3) | |
[[[[1, 0, 0, 0, 0, 0, 0, 0]]], [[[y, 0, 1, 0, 1, 0, 0, 0], | |
[z, 0, 0, 1, 1, 0, 1, 0]], [[x, 1, 0, 0, 1, 1, 0, 0]]], | |
[[[y**2, 0, 2, 0, 2, 0, 0, 0], [y*z, 0, 1, 1, 2, 0, 1, 0], | |
[z**2, 0, 0, 2, 2, 0, 2, 0]], [[x*y, 1, 1, 0, 2, 1, 0, 0], | |
[x*z, 1, 0, 1, 2, 1, 1, 0]], [[x**2, 2, 0, 0, 2, 2, 0, 0]]]] | |
""" | |
if no_of_gens == 2: | |
count = 0 | |
terms = [None] * int((binomial_power ** 2 + 3 * binomial_power + 2) / 2) | |
for x_count in range(0, binomial_power + 1): | |
for y_count in range(0, binomial_power - x_count + 1): | |
terms[count] = [x**x_count*y**y_count, | |
x_count, y_count, 0] | |
count += 1 | |
else: | |
terms = [[[[x ** x_count * y ** y_count * | |
z ** (z_count - y_count - x_count), | |
x_count, y_count, z_count - y_count - x_count, | |
z_count, x_count, z_count - y_count - x_count, 0] | |
for y_count in range(z_count - x_count, -1, -1)] | |
for x_count in range(0, z_count + 1)] | |
for z_count in range(0, binomial_power + 1)] | |
return terms | |
def hyperplane_parameters(poly, vertices=None): | |
"""A helper function to return the hyperplane parameters | |
of which the facets of the polytope are a part of. | |
Parameters | |
========== | |
poly : | |
The input 2/3-Polytope. | |
vertices : | |
Vertex indices of 3-Polytope. | |
Examples | |
======== | |
>>> from sympy import Point, Polygon | |
>>> from sympy.integrals.intpoly import hyperplane_parameters | |
>>> hyperplane_parameters(Polygon(Point(0, 3), Point(5, 3), Point(1, 1))) | |
[((0, 1), 3), ((1, -2), -1), ((-2, -1), -3)] | |
>>> cube = [[(0, 0, 0), (0, 0, 5), (0, 5, 0), (0, 5, 5), (5, 0, 0),\ | |
(5, 0, 5), (5, 5, 0), (5, 5, 5)],\ | |
[2, 6, 7, 3], [3, 7, 5, 1], [7, 6, 4, 5], [1, 5, 4, 0],\ | |
[3, 1, 0, 2], [0, 4, 6, 2]] | |
>>> hyperplane_parameters(cube[1:], cube[0]) | |
[([0, -1, 0], -5), ([0, 0, -1], -5), ([-1, 0, 0], -5), | |
([0, 1, 0], 0), ([1, 0, 0], 0), ([0, 0, 1], 0)] | |
""" | |
if isinstance(poly, Polygon): | |
vertices = list(poly.vertices) + [poly.vertices[0]] # Close the polygon | |
params = [None] * (len(vertices) - 1) | |
for i in range(len(vertices) - 1): | |
v1 = vertices[i] | |
v2 = vertices[i + 1] | |
a1 = v1[1] - v2[1] | |
a2 = v2[0] - v1[0] | |
b = v2[0] * v1[1] - v2[1] * v1[0] | |
factor = gcd_list([a1, a2, b]) | |
b = S(b) / factor | |
a = (S(a1) / factor, S(a2) / factor) | |
params[i] = (a, b) | |
else: | |
params = [None] * len(poly) | |
for i, polygon in enumerate(poly): | |
v1, v2, v3 = [vertices[vertex] for vertex in polygon[:3]] | |
normal = cross_product(v1, v2, v3) | |
b = sum(normal[j] * v1[j] for j in range(0, 3)) | |
fac = gcd_list(normal) | |
if fac.is_zero: | |
fac = 1 | |
normal = [j / fac for j in normal] | |
b = b / fac | |
params[i] = (normal, b) | |
return params | |
def cross_product(v1, v2, v3): | |
"""Returns the cross-product of vectors (v2 - v1) and (v3 - v1) | |
That is : (v2 - v1) X (v3 - v1) | |
""" | |
v2 = [v2[j] - v1[j] for j in range(0, 3)] | |
v3 = [v3[j] - v1[j] for j in range(0, 3)] | |
return [v3[2] * v2[1] - v3[1] * v2[2], | |
v3[0] * v2[2] - v3[2] * v2[0], | |
v3[1] * v2[0] - v3[0] * v2[1]] | |
def best_origin(a, b, lineseg, expr): | |
"""Helper method for polytope_integrate. Currently not used in the main | |
algorithm. | |
Explanation | |
=========== | |
Returns a point on the lineseg whose vector inner product with the | |
divergence of `expr` yields an expression with the least maximum | |
total power. | |
Parameters | |
========== | |
a : | |
Hyperplane parameter denoting direction. | |
b : | |
Hyperplane parameter denoting distance. | |
lineseg : | |
Line segment on which to find the origin. | |
expr : | |
The expression which determines the best point. | |
Algorithm(currently works only for 2D use case) | |
=============================================== | |
1 > Firstly, check for edge cases. Here that would refer to vertical | |
or horizontal lines. | |
2 > If input expression is a polynomial containing more than one generator | |
then find out the total power of each of the generators. | |
x**2 + 3 + x*y + x**4*y**5 ---> {x: 7, y: 6} | |
If expression is a constant value then pick the first boundary point | |
of the line segment. | |
3 > First check if a point exists on the line segment where the value of | |
the highest power generator becomes 0. If not check if the value of | |
the next highest becomes 0. If none becomes 0 within line segment | |
constraints then pick the first boundary point of the line segment. | |
Actually, any point lying on the segment can be picked as best origin | |
in the last case. | |
Examples | |
======== | |
>>> from sympy.integrals.intpoly import best_origin | |
>>> from sympy.abc import x, y | |
>>> from sympy import Point, Segment2D | |
>>> l = Segment2D(Point(0, 3), Point(1, 1)) | |
>>> expr = x**3*y**7 | |
>>> best_origin((2, 1), 3, l, expr) | |
(0, 3.0) | |
""" | |
a1, b1 = lineseg.points[0] | |
def x_axis_cut(ls): | |
"""Returns the point where the input line segment | |
intersects the x-axis. | |
Parameters | |
========== | |
ls : | |
Line segment | |
""" | |
p, q = ls.points | |
if p.y.is_zero: | |
return tuple(p) | |
elif q.y.is_zero: | |
return tuple(q) | |
elif p.y/q.y < S.Zero: | |
return p.y * (p.x - q.x)/(q.y - p.y) + p.x, S.Zero | |
else: | |
return () | |
def y_axis_cut(ls): | |
"""Returns the point where the input line segment | |
intersects the y-axis. | |
Parameters | |
========== | |
ls : | |
Line segment | |
""" | |
p, q = ls.points | |
if p.x.is_zero: | |
return tuple(p) | |
elif q.x.is_zero: | |
return tuple(q) | |
elif p.x/q.x < S.Zero: | |
return S.Zero, p.x * (p.y - q.y)/(q.x - p.x) + p.y | |
else: | |
return () | |
gens = (x, y) | |
power_gens = {} | |
for i in gens: | |
power_gens[i] = S.Zero | |
if len(gens) > 1: | |
# Special case for vertical and horizontal lines | |
if len(gens) == 2: | |
if a[0] == 0: | |
if y_axis_cut(lineseg): | |
return S.Zero, b/a[1] | |
else: | |
return a1, b1 | |
elif a[1] == 0: | |
if x_axis_cut(lineseg): | |
return b/a[0], S.Zero | |
else: | |
return a1, b1 | |
if isinstance(expr, Expr): # Find the sum total of power of each | |
if expr.is_Add: # generator and store in a dictionary. | |
for monomial in expr.args: | |
if monomial.is_Pow: | |
if monomial.args[0] in gens: | |
power_gens[monomial.args[0]] += monomial.args[1] | |
else: | |
for univariate in monomial.args: | |
term_type = len(univariate.args) | |
if term_type == 0 and univariate in gens: | |
power_gens[univariate] += 1 | |
elif term_type == 2 and univariate.args[0] in gens: | |
power_gens[univariate.args[0]] +=\ | |
univariate.args[1] | |
elif expr.is_Mul: | |
for term in expr.args: | |
term_type = len(term.args) | |
if term_type == 0 and term in gens: | |
power_gens[term] += 1 | |
elif term_type == 2 and term.args[0] in gens: | |
power_gens[term.args[0]] += term.args[1] | |
elif expr.is_Pow: | |
power_gens[expr.args[0]] = expr.args[1] | |
elif expr.is_Symbol: | |
power_gens[expr] += 1 | |
else: # If `expr` is a constant take first vertex of the line segment. | |
return a1, b1 | |
# TODO : This part is quite hacky. Should be made more robust with | |
# TODO : respect to symbol names and scalable w.r.t higher dimensions. | |
power_gens = sorted(power_gens.items(), key=lambda k: str(k[0])) | |
if power_gens[0][1] >= power_gens[1][1]: | |
if y_axis_cut(lineseg): | |
x0 = (S.Zero, b / a[1]) | |
elif x_axis_cut(lineseg): | |
x0 = (b / a[0], S.Zero) | |
else: | |
x0 = (a1, b1) | |
else: | |
if x_axis_cut(lineseg): | |
x0 = (b/a[0], S.Zero) | |
elif y_axis_cut(lineseg): | |
x0 = (S.Zero, b/a[1]) | |
else: | |
x0 = (a1, b1) | |
else: | |
x0 = (b/a[0]) | |
return x0 | |
def decompose(expr, separate=False): | |
"""Decomposes an input polynomial into homogeneous ones of | |
smaller or equal degree. | |
Explanation | |
=========== | |
Returns a dictionary with keys as the degree of the smaller | |
constituting polynomials. Values are the constituting polynomials. | |
Parameters | |
========== | |
expr : Expr | |
Polynomial(SymPy expression). | |
separate : bool | |
If True then simply return a list of the constituent monomials | |
If not then break up the polynomial into constituent homogeneous | |
polynomials. | |
Examples | |
======== | |
>>> from sympy.abc import x, y | |
>>> from sympy.integrals.intpoly import decompose | |
>>> decompose(x**2 + x*y + x + y + x**3*y**2 + y**5) | |
{1: x + y, 2: x**2 + x*y, 5: x**3*y**2 + y**5} | |
>>> decompose(x**2 + x*y + x + y + x**3*y**2 + y**5, True) | |
{x, x**2, y, y**5, x*y, x**3*y**2} | |
""" | |
poly_dict = {} | |
if isinstance(expr, Expr) and not expr.is_number: | |
if expr.is_Symbol: | |
poly_dict[1] = expr | |
elif expr.is_Add: | |
symbols = expr.atoms(Symbol) | |
degrees = [(sum(degree_list(monom, *symbols)), monom) | |
for monom in expr.args] | |
if separate: | |
return {monom[1] for monom in degrees} | |
else: | |
for monom in degrees: | |
degree, term = monom | |
if poly_dict.get(degree): | |
poly_dict[degree] += term | |
else: | |
poly_dict[degree] = term | |
elif expr.is_Pow: | |
_, degree = expr.args | |
poly_dict[degree] = expr | |
else: # Now expr can only be of `Mul` type | |
degree = 0 | |
for term in expr.args: | |
term_type = len(term.args) | |
if term_type == 0 and term.is_Symbol: | |
degree += 1 | |
elif term_type == 2: | |
degree += term.args[1] | |
poly_dict[degree] = expr | |
else: | |
poly_dict[0] = expr | |
if separate: | |
return set(poly_dict.values()) | |
return poly_dict | |
def point_sort(poly, normal=None, clockwise=True): | |
"""Returns the same polygon with points sorted in clockwise or | |
anti-clockwise order. | |
Note that it's necessary for input points to be sorted in some order | |
(clockwise or anti-clockwise) for the integration algorithm to work. | |
As a convention algorithm has been implemented keeping clockwise | |
orientation in mind. | |
Parameters | |
========== | |
poly: | |
2D or 3D Polygon. | |
normal : optional | |
The normal of the plane which the 3-Polytope is a part of. | |
clockwise : bool, optional | |
Returns points sorted in clockwise order if True and | |
anti-clockwise if False. | |
Examples | |
======== | |
>>> from sympy.integrals.intpoly import point_sort | |
>>> from sympy import Point | |
>>> point_sort([Point(0, 0), Point(1, 0), Point(1, 1)]) | |
[Point2D(1, 1), Point2D(1, 0), Point2D(0, 0)] | |
""" | |
pts = poly.vertices if isinstance(poly, Polygon) else poly | |
n = len(pts) | |
if n < 2: | |
return list(pts) | |
order = S.One if clockwise else S.NegativeOne | |
dim = len(pts[0]) | |
if dim == 2: | |
center = Point(sum((vertex.x for vertex in pts)) / n, | |
sum((vertex.y for vertex in pts)) / n) | |
else: | |
center = Point(sum((vertex.x for vertex in pts)) / n, | |
sum((vertex.y for vertex in pts)) / n, | |
sum((vertex.z for vertex in pts)) / n) | |
def compare(a, b): | |
if a.x - center.x >= S.Zero and b.x - center.x < S.Zero: | |
return -order | |
elif a.x - center.x < 0 and b.x - center.x >= 0: | |
return order | |
elif a.x - center.x == 0 and b.x - center.x == 0: | |
if a.y - center.y >= 0 or b.y - center.y >= 0: | |
return -order if a.y > b.y else order | |
return -order if b.y > a.y else order | |
det = (a.x - center.x) * (b.y - center.y) -\ | |
(b.x - center.x) * (a.y - center.y) | |
if det < 0: | |
return -order | |
elif det > 0: | |
return order | |
first = (a.x - center.x) * (a.x - center.x) +\ | |
(a.y - center.y) * (a.y - center.y) | |
second = (b.x - center.x) * (b.x - center.x) +\ | |
(b.y - center.y) * (b.y - center.y) | |
return -order if first > second else order | |
def compare3d(a, b): | |
det = cross_product(center, a, b) | |
dot_product = sum(det[i] * normal[i] for i in range(0, 3)) | |
if dot_product < 0: | |
return -order | |
elif dot_product > 0: | |
return order | |
return sorted(pts, key=cmp_to_key(compare if dim==2 else compare3d)) | |
def norm(point): | |
"""Returns the Euclidean norm of a point from origin. | |
Parameters | |
========== | |
point: | |
This denotes a point in the dimension_al spac_e. | |
Examples | |
======== | |
>>> from sympy.integrals.intpoly import norm | |
>>> from sympy import Point | |
>>> norm(Point(2, 7)) | |
sqrt(53) | |
""" | |
half = S.Half | |
if isinstance(point, (list, tuple)): | |
return sum(coord ** 2 for coord in point) ** half | |
elif isinstance(point, Point): | |
if isinstance(point, Point2D): | |
return (point.x ** 2 + point.y ** 2) ** half | |
else: | |
return (point.x ** 2 + point.y ** 2 + point.z) ** half | |
elif isinstance(point, dict): | |
return sum(i**2 for i in point.values()) ** half | |
def intersection(geom_1, geom_2, intersection_type): | |
"""Returns intersection between geometric objects. | |
Explanation | |
=========== | |
Note that this function is meant for use in integration_reduction and | |
at that point in the calling function the lines denoted by the segments | |
surely intersect within segment boundaries. Coincident lines are taken | |
to be non-intersecting. Also, the hyperplane intersection for 2D case is | |
also implemented. | |
Parameters | |
========== | |
geom_1, geom_2: | |
The input line segments. | |
Examples | |
======== | |
>>> from sympy.integrals.intpoly import intersection | |
>>> from sympy import Point, Segment2D | |
>>> l1 = Segment2D(Point(1, 1), Point(3, 5)) | |
>>> l2 = Segment2D(Point(2, 0), Point(2, 5)) | |
>>> intersection(l1, l2, "segment2D") | |
(2, 3) | |
>>> p1 = ((-1, 0), 0) | |
>>> p2 = ((0, 1), 1) | |
>>> intersection(p1, p2, "plane2D") | |
(0, 1) | |
""" | |
if intersection_type[:-2] == "segment": | |
if intersection_type == "segment2D": | |
x1, y1 = geom_1.points[0] | |
x2, y2 = geom_1.points[1] | |
x3, y3 = geom_2.points[0] | |
x4, y4 = geom_2.points[1] | |
elif intersection_type == "segment3D": | |
x1, y1, z1 = geom_1.points[0] | |
x2, y2, z2 = geom_1.points[1] | |
x3, y3, z3 = geom_2.points[0] | |
x4, y4, z4 = geom_2.points[1] | |
denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4) | |
if denom: | |
t1 = x1 * y2 - y1 * x2 | |
t2 = x3 * y4 - x4 * y3 | |
return (S(t1 * (x3 - x4) - t2 * (x1 - x2)) / denom, | |
S(t1 * (y3 - y4) - t2 * (y1 - y2)) / denom) | |
if intersection_type[:-2] == "plane": | |
if intersection_type == "plane2D": # Intersection of hyperplanes | |
a1x, a1y = geom_1[0] | |
a2x, a2y = geom_2[0] | |
b1, b2 = geom_1[1], geom_2[1] | |
denom = a1x * a2y - a2x * a1y | |
if denom: | |
return (S(b1 * a2y - b2 * a1y) / denom, | |
S(b2 * a1x - b1 * a2x) / denom) | |
def is_vertex(ent): | |
"""If the input entity is a vertex return True. | |
Parameter | |
========= | |
ent : | |
Denotes a geometric entity representing a point. | |
Examples | |
======== | |
>>> from sympy import Point | |
>>> from sympy.integrals.intpoly import is_vertex | |
>>> is_vertex((2, 3)) | |
True | |
>>> is_vertex((2, 3, 6)) | |
True | |
>>> is_vertex(Point(2, 3)) | |
True | |
""" | |
if isinstance(ent, tuple): | |
if len(ent) in [2, 3]: | |
return True | |
elif isinstance(ent, Point): | |
return True | |
return False | |
def plot_polytope(poly): | |
"""Plots the 2D polytope using the functions written in plotting | |
module which in turn uses matplotlib backend. | |
Parameter | |
========= | |
poly: | |
Denotes a 2-Polytope. | |
""" | |
from sympy.plotting.plot import Plot, List2DSeries | |
xl = [vertex.x for vertex in poly.vertices] | |
yl = [vertex.y for vertex in poly.vertices] | |
xl.append(poly.vertices[0].x) # Closing the polygon | |
yl.append(poly.vertices[0].y) | |
l2ds = List2DSeries(xl, yl) | |
p = Plot(l2ds, axes='label_axes=True') | |
p.show() | |
def plot_polynomial(expr): | |
"""Plots the polynomial using the functions written in | |
plotting module which in turn uses matplotlib backend. | |
Parameter | |
========= | |
expr: | |
Denotes a polynomial(SymPy expression). | |
""" | |
from sympy.plotting.plot import plot3d, plot | |
gens = expr.free_symbols | |
if len(gens) == 2: | |
plot3d(expr) | |
else: | |
plot(expr) | |