|
""" |
|
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): |
|
|
|
hp_params = hyperplane_parameters(poly) |
|
facets = poly.sides |
|
elif len(poly[0]) == 2: |
|
|
|
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: |
|
|
|
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): |
|
|
|
|
|
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): |
|
|
|
|
|
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: |
|
|
|
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]] |
|
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: |
|
|
|
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): |
|
if expr.is_Add: |
|
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: |
|
return a1, b1 |
|
|
|
|
|
|
|
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: |
|
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": |
|
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) |
|
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) |
|
|