12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283 |
- """
- 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
- 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(0, 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 be must'
- 'be a valid SymPy expression')
- return main_integrate3d(expr, facets, vertices, hp_params)
- if max_degree is not None:
- result = {}
- 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 be 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 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 = {}
- integral_value = S.Zero
- 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:
- polynomials = decompose(expr)
- 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 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]]
- for i in range(len(facet)):
- side = (vertices[facet[i]], vertices[facet[(i + 1) % len(facet)]])
- 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(0, 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]]
- for i in range(len(facet)):
- side = (vertices[facet[i]], vertices[facet[(i + 1) % len(facet)]])
- 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(map(lambda vertex: vertex.x, pts)) / n,
- sum(map(lambda vertex: vertex.y, pts)) / n)
- else:
- center = Point(sum(map(lambda vertex: vertex.x, pts)) / n,
- sum(map(lambda vertex: vertex.y, pts)) / n,
- sum(map(lambda vertex: vertex.z, 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 = list(map(lambda vertex: vertex.x, poly.vertices))
- yl = list(map(lambda vertex: vertex.y, 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)
|