intpoly.py 42 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283
  1. """
  2. Module to implement integration of uni/bivariate polynomials over
  3. 2D Polytopes and uni/bi/trivariate polynomials over 3D Polytopes.
  4. Uses evaluation techniques as described in Chin et al. (2015) [1].
  5. References
  6. ===========
  7. .. [1] Chin, Eric B., Jean B. Lasserre, and N. Sukumar. "Numerical integration
  8. of homogeneous functions on convex and nonconvex polygons and polyhedra."
  9. Computational Mechanics 56.6 (2015): 967-981
  10. PDF link : http://dilbert.engr.ucdavis.edu/~suku/quadrature/cls-integration.pdf
  11. """
  12. from functools import cmp_to_key
  13. from sympy.abc import x, y, z
  14. from sympy.core import S, diff, Expr, Symbol
  15. from sympy.core.sympify import _sympify
  16. from sympy.geometry import Segment2D, Polygon, Point, Point2D
  17. from sympy.polys.polytools import LC, gcd_list, degree_list
  18. from sympy.simplify.simplify import nsimplify
  19. def polytope_integrate(poly, expr=None, *, clockwise=False, max_degree=None):
  20. """Integrates polynomials over 2/3-Polytopes.
  21. Explanation
  22. ===========
  23. This function accepts the polytope in ``poly`` and the function in ``expr``
  24. (uni/bi/trivariate polynomials are implemented) and returns
  25. the exact integral of ``expr`` over ``poly``.
  26. Parameters
  27. ==========
  28. poly : The input Polygon.
  29. expr : The input polynomial.
  30. clockwise : Binary value to sort input points of 2-Polytope clockwise.(Optional)
  31. max_degree : The maximum degree of any monomial of the input polynomial.(Optional)
  32. Examples
  33. ========
  34. >>> from sympy.abc import x, y
  35. >>> from sympy import Point, Polygon
  36. >>> from sympy.integrals.intpoly import polytope_integrate
  37. >>> polygon = Polygon(Point(0, 0), Point(0, 1), Point(1, 1), Point(1, 0))
  38. >>> polys = [1, x, y, x*y, x**2*y, x*y**2]
  39. >>> expr = x*y
  40. >>> polytope_integrate(polygon, expr)
  41. 1/4
  42. >>> polytope_integrate(polygon, polys, max_degree=3)
  43. {1: 1, x: 1/2, y: 1/2, x*y: 1/4, x*y**2: 1/6, x**2*y: 1/6}
  44. """
  45. if clockwise:
  46. if isinstance(poly, Polygon):
  47. poly = Polygon(*point_sort(poly.vertices), evaluate=False)
  48. else:
  49. raise TypeError("clockwise=True works for only 2-Polytope"
  50. "V-representation input")
  51. if isinstance(poly, Polygon):
  52. # For Vertex Representation(2D case)
  53. hp_params = hyperplane_parameters(poly)
  54. facets = poly.sides
  55. elif len(poly[0]) == 2:
  56. # For Hyperplane Representation(2D case)
  57. plen = len(poly)
  58. if len(poly[0][0]) == 2:
  59. intersections = [intersection(poly[(i - 1) % plen], poly[i],
  60. "plane2D")
  61. for i in range(0, plen)]
  62. hp_params = poly
  63. lints = len(intersections)
  64. facets = [Segment2D(intersections[i],
  65. intersections[(i + 1) % lints])
  66. for i in range(0, lints)]
  67. else:
  68. raise NotImplementedError("Integration for H-representation 3D"
  69. "case not implemented yet.")
  70. else:
  71. # For Vertex Representation(3D case)
  72. vertices = poly[0]
  73. facets = poly[1:]
  74. hp_params = hyperplane_parameters(facets, vertices)
  75. if max_degree is None:
  76. if expr is None:
  77. raise TypeError('Input expression be must'
  78. 'be a valid SymPy expression')
  79. return main_integrate3d(expr, facets, vertices, hp_params)
  80. if max_degree is not None:
  81. result = {}
  82. if not isinstance(expr, list) and expr is not None:
  83. raise TypeError('Input polynomials must be list of expressions')
  84. if len(hp_params[0][0]) == 3:
  85. result_dict = main_integrate3d(0, facets, vertices, hp_params,
  86. max_degree)
  87. else:
  88. result_dict = main_integrate(0, facets, hp_params, max_degree)
  89. if expr is None:
  90. return result_dict
  91. for poly in expr:
  92. poly = _sympify(poly)
  93. if poly not in result:
  94. if poly.is_zero:
  95. result[S.Zero] = S.Zero
  96. continue
  97. integral_value = S.Zero
  98. monoms = decompose(poly, separate=True)
  99. for monom in monoms:
  100. monom = nsimplify(monom)
  101. coeff, m = strip(monom)
  102. integral_value += result_dict[m] * coeff
  103. result[poly] = integral_value
  104. return result
  105. if expr is None:
  106. raise TypeError('Input expression be must'
  107. 'be a valid SymPy expression')
  108. return main_integrate(expr, facets, hp_params)
  109. def strip(monom):
  110. if monom.is_zero:
  111. return S.Zero, S.Zero
  112. elif monom.is_number:
  113. return monom, S.One
  114. else:
  115. coeff = LC(monom)
  116. return coeff, monom / coeff
  117. def main_integrate3d(expr, facets, vertices, hp_params, max_degree=None):
  118. """Function to translate the problem of integrating uni/bi/tri-variate
  119. polynomials over a 3-Polytope to integrating over its faces.
  120. This is done using Generalized Stokes' Theorem and Euler's Theorem.
  121. Parameters
  122. ==========
  123. expr :
  124. The input polynomial.
  125. facets :
  126. Faces of the 3-Polytope(expressed as indices of `vertices`).
  127. vertices :
  128. Vertices that constitute the Polytope.
  129. hp_params :
  130. Hyperplane Parameters of the facets.
  131. max_degree : optional
  132. Max degree of constituent monomial in given list of polynomial.
  133. Examples
  134. ========
  135. >>> from sympy.integrals.intpoly import main_integrate3d, \
  136. hyperplane_parameters
  137. >>> cube = [[(0, 0, 0), (0, 0, 5), (0, 5, 0), (0, 5, 5), (5, 0, 0),\
  138. (5, 0, 5), (5, 5, 0), (5, 5, 5)],\
  139. [2, 6, 7, 3], [3, 7, 5, 1], [7, 6, 4, 5], [1, 5, 4, 0],\
  140. [3, 1, 0, 2], [0, 4, 6, 2]]
  141. >>> vertices = cube[0]
  142. >>> faces = cube[1:]
  143. >>> hp_params = hyperplane_parameters(faces, vertices)
  144. >>> main_integrate3d(1, faces, vertices, hp_params)
  145. -125
  146. """
  147. result = {}
  148. dims = (x, y, z)
  149. dim_length = len(dims)
  150. if max_degree:
  151. grad_terms = gradient_terms(max_degree, 3)
  152. flat_list = [term for z_terms in grad_terms
  153. for x_term in z_terms
  154. for term in x_term]
  155. for term in flat_list:
  156. result[term[0]] = 0
  157. for facet_count, hp in enumerate(hp_params):
  158. a, b = hp[0], hp[1]
  159. x0 = vertices[facets[facet_count][0]]
  160. for i, monom in enumerate(flat_list):
  161. # Every monomial is a tuple :
  162. # (term, x_degree, y_degree, z_degree, value over boundary)
  163. expr, x_d, y_d, z_d, z_index, y_index, x_index, _ = monom
  164. degree = x_d + y_d + z_d
  165. if b.is_zero:
  166. value_over_face = S.Zero
  167. else:
  168. value_over_face = \
  169. integration_reduction_dynamic(facets, facet_count, a,
  170. b, expr, degree, dims,
  171. x_index, y_index,
  172. z_index, x0, grad_terms,
  173. i, vertices, hp)
  174. monom[7] = value_over_face
  175. result[expr] += value_over_face * \
  176. (b / norm(a)) / (dim_length + x_d + y_d + z_d)
  177. return result
  178. else:
  179. integral_value = S.Zero
  180. polynomials = decompose(expr)
  181. for deg in polynomials:
  182. poly_contribute = S.Zero
  183. facet_count = 0
  184. for i, facet in enumerate(facets):
  185. hp = hp_params[i]
  186. if hp[1].is_zero:
  187. continue
  188. pi = polygon_integrate(facet, hp, i, facets, vertices, expr, deg)
  189. poly_contribute += pi *\
  190. (hp[1] / norm(tuple(hp[0])))
  191. facet_count += 1
  192. poly_contribute /= (dim_length + deg)
  193. integral_value += poly_contribute
  194. return integral_value
  195. def main_integrate(expr, facets, hp_params, max_degree=None):
  196. """Function to translate the problem of integrating univariate/bivariate
  197. polynomials over a 2-Polytope to integrating over its boundary facets.
  198. This is done using Generalized Stokes's Theorem and Euler's Theorem.
  199. Parameters
  200. ==========
  201. expr :
  202. The input polynomial.
  203. facets :
  204. Facets(Line Segments) of the 2-Polytope.
  205. hp_params :
  206. Hyperplane Parameters of the facets.
  207. max_degree : optional
  208. The maximum degree of any monomial of the input polynomial.
  209. >>> from sympy.abc import x, y
  210. >>> from sympy.integrals.intpoly import main_integrate,\
  211. hyperplane_parameters
  212. >>> from sympy import Point, Polygon
  213. >>> triangle = Polygon(Point(0, 3), Point(5, 3), Point(1, 1))
  214. >>> facets = triangle.sides
  215. >>> hp_params = hyperplane_parameters(triangle)
  216. >>> main_integrate(x**2 + y**2, facets, hp_params)
  217. 325/6
  218. """
  219. dims = (x, y)
  220. dim_length = len(dims)
  221. result = {}
  222. integral_value = S.Zero
  223. if max_degree:
  224. grad_terms = [[0, 0, 0, 0]] + gradient_terms(max_degree)
  225. for facet_count, hp in enumerate(hp_params):
  226. a, b = hp[0], hp[1]
  227. x0 = facets[facet_count].points[0]
  228. for i, monom in enumerate(grad_terms):
  229. # Every monomial is a tuple :
  230. # (term, x_degree, y_degree, value over boundary)
  231. m, x_d, y_d, _ = monom
  232. value = result.get(m, None)
  233. degree = S.Zero
  234. if b.is_zero:
  235. value_over_boundary = S.Zero
  236. else:
  237. degree = x_d + y_d
  238. value_over_boundary = \
  239. integration_reduction_dynamic(facets, facet_count, a,
  240. b, m, degree, dims, x_d,
  241. y_d, max_degree, x0,
  242. grad_terms, i)
  243. monom[3] = value_over_boundary
  244. if value is not None:
  245. result[m] += value_over_boundary * \
  246. (b / norm(a)) / (dim_length + degree)
  247. else:
  248. result[m] = value_over_boundary * \
  249. (b / norm(a)) / (dim_length + degree)
  250. return result
  251. else:
  252. polynomials = decompose(expr)
  253. for deg in polynomials:
  254. poly_contribute = S.Zero
  255. facet_count = 0
  256. for hp in hp_params:
  257. value_over_boundary = integration_reduction(facets,
  258. facet_count,
  259. hp[0], hp[1],
  260. polynomials[deg],
  261. dims, deg)
  262. poly_contribute += value_over_boundary * (hp[1] / norm(hp[0]))
  263. facet_count += 1
  264. poly_contribute /= (dim_length + deg)
  265. integral_value += poly_contribute
  266. return integral_value
  267. def polygon_integrate(facet, hp_param, index, facets, vertices, expr, degree):
  268. """Helper function to integrate the input uni/bi/trivariate polynomial
  269. over a certain face of the 3-Polytope.
  270. Parameters
  271. ==========
  272. facet :
  273. Particular face of the 3-Polytope over which ``expr`` is integrated.
  274. index :
  275. The index of ``facet`` in ``facets``.
  276. facets :
  277. Faces of the 3-Polytope(expressed as indices of `vertices`).
  278. vertices :
  279. Vertices that constitute the facet.
  280. expr :
  281. The input polynomial.
  282. degree :
  283. Degree of ``expr``.
  284. Examples
  285. ========
  286. >>> from sympy.integrals.intpoly import polygon_integrate
  287. >>> cube = [[(0, 0, 0), (0, 0, 5), (0, 5, 0), (0, 5, 5), (5, 0, 0),\
  288. (5, 0, 5), (5, 5, 0), (5, 5, 5)],\
  289. [2, 6, 7, 3], [3, 7, 5, 1], [7, 6, 4, 5], [1, 5, 4, 0],\
  290. [3, 1, 0, 2], [0, 4, 6, 2]]
  291. >>> facet = cube[1]
  292. >>> facets = cube[1:]
  293. >>> vertices = cube[0]
  294. >>> polygon_integrate(facet, [(0, 1, 0), 5], 0, facets, vertices, 1, 0)
  295. -25
  296. """
  297. expr = S(expr)
  298. if expr.is_zero:
  299. return S.Zero
  300. result = S.Zero
  301. x0 = vertices[facet[0]]
  302. for i in range(len(facet)):
  303. side = (vertices[facet[i]], vertices[facet[(i + 1) % len(facet)]])
  304. result += distance_to_side(x0, side, hp_param[0]) *\
  305. lineseg_integrate(facet, i, side, expr, degree)
  306. if not expr.is_number:
  307. expr = diff(expr, x) * x0[0] + diff(expr, y) * x0[1] +\
  308. diff(expr, z) * x0[2]
  309. result += polygon_integrate(facet, hp_param, index, facets, vertices,
  310. expr, degree - 1)
  311. result /= (degree + 2)
  312. return result
  313. def distance_to_side(point, line_seg, A):
  314. """Helper function to compute the signed distance between given 3D point
  315. and a line segment.
  316. Parameters
  317. ==========
  318. point : 3D Point
  319. line_seg : Line Segment
  320. Examples
  321. ========
  322. >>> from sympy.integrals.intpoly import distance_to_side
  323. >>> point = (0, 0, 0)
  324. >>> distance_to_side(point, [(0, 0, 1), (0, 1, 0)], (1, 0, 0))
  325. -sqrt(2)/2
  326. """
  327. x1, x2 = line_seg
  328. rev_normal = [-1 * S(i)/norm(A) for i in A]
  329. vector = [x2[i] - x1[i] for i in range(0, 3)]
  330. vector = [vector[i]/norm(vector) for i in range(0, 3)]
  331. n_side = cross_product((0, 0, 0), rev_normal, vector)
  332. vectorx0 = [line_seg[0][i] - point[i] for i in range(0, 3)]
  333. dot_product = sum([vectorx0[i] * n_side[i] for i in range(0, 3)])
  334. return dot_product
  335. def lineseg_integrate(polygon, index, line_seg, expr, degree):
  336. """Helper function to compute the line integral of ``expr`` over ``line_seg``.
  337. Parameters
  338. ===========
  339. polygon :
  340. Face of a 3-Polytope.
  341. index :
  342. Index of line_seg in polygon.
  343. line_seg :
  344. Line Segment.
  345. Examples
  346. ========
  347. >>> from sympy.integrals.intpoly import lineseg_integrate
  348. >>> polygon = [(0, 5, 0), (5, 5, 0), (5, 5, 5), (0, 5, 5)]
  349. >>> line_seg = [(0, 5, 0), (5, 5, 0)]
  350. >>> lineseg_integrate(polygon, 0, line_seg, 1, 0)
  351. 5
  352. """
  353. expr = _sympify(expr)
  354. if expr.is_zero:
  355. return S.Zero
  356. result = S.Zero
  357. x0 = line_seg[0]
  358. distance = norm(tuple([line_seg[1][i] - line_seg[0][i] for i in
  359. range(3)]))
  360. if isinstance(expr, Expr):
  361. expr_dict = {x: line_seg[1][0],
  362. y: line_seg[1][1],
  363. z: line_seg[1][2]}
  364. result += distance * expr.subs(expr_dict)
  365. else:
  366. result += distance * expr
  367. expr = diff(expr, x) * x0[0] + diff(expr, y) * x0[1] +\
  368. diff(expr, z) * x0[2]
  369. result += lineseg_integrate(polygon, index, line_seg, expr, degree - 1)
  370. result /= (degree + 1)
  371. return result
  372. def integration_reduction(facets, index, a, b, expr, dims, degree):
  373. """Helper method for main_integrate. Returns the value of the input
  374. expression evaluated over the polytope facet referenced by a given index.
  375. Parameters
  376. ===========
  377. facets :
  378. List of facets of the polytope.
  379. index :
  380. Index referencing the facet to integrate the expression over.
  381. a :
  382. Hyperplane parameter denoting direction.
  383. b :
  384. Hyperplane parameter denoting distance.
  385. expr :
  386. The expression to integrate over the facet.
  387. dims :
  388. List of symbols denoting axes.
  389. degree :
  390. Degree of the homogeneous polynomial.
  391. Examples
  392. ========
  393. >>> from sympy.abc import x, y
  394. >>> from sympy.integrals.intpoly import integration_reduction,\
  395. hyperplane_parameters
  396. >>> from sympy import Point, Polygon
  397. >>> triangle = Polygon(Point(0, 3), Point(5, 3), Point(1, 1))
  398. >>> facets = triangle.sides
  399. >>> a, b = hyperplane_parameters(triangle)[0]
  400. >>> integration_reduction(facets, 0, a, b, 1, (x, y), 0)
  401. 5
  402. """
  403. expr = _sympify(expr)
  404. if expr.is_zero:
  405. return expr
  406. value = S.Zero
  407. x0 = facets[index].points[0]
  408. m = len(facets)
  409. gens = (x, y)
  410. inner_product = diff(expr, gens[0]) * x0[0] + diff(expr, gens[1]) * x0[1]
  411. if inner_product != 0:
  412. value += integration_reduction(facets, index, a, b,
  413. inner_product, dims, degree - 1)
  414. value += left_integral2D(m, index, facets, x0, expr, gens)
  415. return value/(len(dims) + degree - 1)
  416. def left_integral2D(m, index, facets, x0, expr, gens):
  417. """Computes the left integral of Eq 10 in Chin et al.
  418. For the 2D case, the integral is just an evaluation of the polynomial
  419. at the intersection of two facets which is multiplied by the distance
  420. between the first point of facet and that intersection.
  421. Parameters
  422. ==========
  423. m :
  424. No. of hyperplanes.
  425. index :
  426. Index of facet to find intersections with.
  427. facets :
  428. List of facets(Line Segments in 2D case).
  429. x0 :
  430. First point on facet referenced by index.
  431. expr :
  432. Input polynomial
  433. gens :
  434. Generators which generate the polynomial
  435. Examples
  436. ========
  437. >>> from sympy.abc import x, y
  438. >>> from sympy.integrals.intpoly import left_integral2D
  439. >>> from sympy import Point, Polygon
  440. >>> triangle = Polygon(Point(0, 3), Point(5, 3), Point(1, 1))
  441. >>> facets = triangle.sides
  442. >>> left_integral2D(3, 0, facets, facets[0].points[0], 1, (x, y))
  443. 5
  444. """
  445. value = S.Zero
  446. for j in range(0, m):
  447. intersect = ()
  448. if j in ((index - 1) % m, (index + 1) % m):
  449. intersect = intersection(facets[index], facets[j], "segment2D")
  450. if intersect:
  451. distance_origin = norm(tuple(map(lambda x, y: x - y,
  452. intersect, x0)))
  453. if is_vertex(intersect):
  454. if isinstance(expr, Expr):
  455. if len(gens) == 3:
  456. expr_dict = {gens[0]: intersect[0],
  457. gens[1]: intersect[1],
  458. gens[2]: intersect[2]}
  459. else:
  460. expr_dict = {gens[0]: intersect[0],
  461. gens[1]: intersect[1]}
  462. value += distance_origin * expr.subs(expr_dict)
  463. else:
  464. value += distance_origin * expr
  465. return value
  466. def integration_reduction_dynamic(facets, index, a, b, expr, degree, dims,
  467. x_index, y_index, max_index, x0,
  468. monomial_values, monom_index, vertices=None,
  469. hp_param=None):
  470. """The same integration_reduction function which uses a dynamic
  471. programming approach to compute terms by using the values of the integral
  472. of previously computed terms.
  473. Parameters
  474. ==========
  475. facets :
  476. Facets of the Polytope.
  477. index :
  478. Index of facet to find intersections with.(Used in left_integral()).
  479. a, b :
  480. Hyperplane parameters.
  481. expr :
  482. Input monomial.
  483. degree :
  484. Total degree of ``expr``.
  485. dims :
  486. Tuple denoting axes variables.
  487. x_index :
  488. Exponent of 'x' in ``expr``.
  489. y_index :
  490. Exponent of 'y' in ``expr``.
  491. max_index :
  492. Maximum exponent of any monomial in ``monomial_values``.
  493. x0 :
  494. First point on ``facets[index]``.
  495. monomial_values :
  496. List of monomial values constituting the polynomial.
  497. monom_index :
  498. Index of monomial whose integration is being found.
  499. vertices : optional
  500. Coordinates of vertices constituting the 3-Polytope.
  501. hp_param : optional
  502. Hyperplane Parameter of the face of the facets[index].
  503. Examples
  504. ========
  505. >>> from sympy.abc import x, y
  506. >>> from sympy.integrals.intpoly import (integration_reduction_dynamic, \
  507. hyperplane_parameters)
  508. >>> from sympy import Point, Polygon
  509. >>> triangle = Polygon(Point(0, 3), Point(5, 3), Point(1, 1))
  510. >>> facets = triangle.sides
  511. >>> a, b = hyperplane_parameters(triangle)[0]
  512. >>> x0 = facets[0].points[0]
  513. >>> monomial_values = [[0, 0, 0, 0], [1, 0, 0, 5],\
  514. [y, 0, 1, 15], [x, 1, 0, None]]
  515. >>> integration_reduction_dynamic(facets, 0, a, b, x, 1, (x, y), 1, 0, 1,\
  516. x0, monomial_values, 3)
  517. 25/2
  518. """
  519. value = S.Zero
  520. m = len(facets)
  521. if expr == S.Zero:
  522. return expr
  523. if len(dims) == 2:
  524. if not expr.is_number:
  525. _, x_degree, y_degree, _ = monomial_values[monom_index]
  526. x_index = monom_index - max_index + \
  527. x_index - 2 if x_degree > 0 else 0
  528. y_index = monom_index - 1 if y_degree > 0 else 0
  529. x_value, y_value =\
  530. monomial_values[x_index][3], monomial_values[y_index][3]
  531. value += x_degree * x_value * x0[0] + y_degree * y_value * x0[1]
  532. value += left_integral2D(m, index, facets, x0, expr, dims)
  533. else:
  534. # For 3D use case the max_index contains the z_degree of the term
  535. z_index = max_index
  536. if not expr.is_number:
  537. x_degree, y_degree, z_degree = y_index,\
  538. z_index - x_index - y_index, x_index
  539. x_value = monomial_values[z_index - 1][y_index - 1][x_index][7]\
  540. if x_degree > 0 else 0
  541. y_value = monomial_values[z_index - 1][y_index][x_index][7]\
  542. if y_degree > 0 else 0
  543. z_value = monomial_values[z_index - 1][y_index][x_index - 1][7]\
  544. if z_degree > 0 else 0
  545. value += x_degree * x_value * x0[0] + y_degree * y_value * x0[1] \
  546. + z_degree * z_value * x0[2]
  547. value += left_integral3D(facets, index, expr,
  548. vertices, hp_param, degree)
  549. return value / (len(dims) + degree - 1)
  550. def left_integral3D(facets, index, expr, vertices, hp_param, degree):
  551. """Computes the left integral of Eq 10 in Chin et al.
  552. Explanation
  553. ===========
  554. For the 3D case, this is the sum of the integral values over constituting
  555. line segments of the face (which is accessed by facets[index]) multiplied
  556. by the distance between the first point of facet and that line segment.
  557. Parameters
  558. ==========
  559. facets :
  560. List of faces of the 3-Polytope.
  561. index :
  562. Index of face over which integral is to be calculated.
  563. expr :
  564. Input polynomial.
  565. vertices :
  566. List of vertices that constitute the 3-Polytope.
  567. hp_param :
  568. The hyperplane parameters of the face.
  569. degree :
  570. Degree of the ``expr``.
  571. Examples
  572. ========
  573. >>> from sympy.integrals.intpoly import left_integral3D
  574. >>> cube = [[(0, 0, 0), (0, 0, 5), (0, 5, 0), (0, 5, 5), (5, 0, 0),\
  575. (5, 0, 5), (5, 5, 0), (5, 5, 5)],\
  576. [2, 6, 7, 3], [3, 7, 5, 1], [7, 6, 4, 5], [1, 5, 4, 0],\
  577. [3, 1, 0, 2], [0, 4, 6, 2]]
  578. >>> facets = cube[1:]
  579. >>> vertices = cube[0]
  580. >>> left_integral3D(facets, 3, 1, vertices, ([0, -1, 0], -5), 0)
  581. -50
  582. """
  583. value = S.Zero
  584. facet = facets[index]
  585. x0 = vertices[facet[0]]
  586. for i in range(len(facet)):
  587. side = (vertices[facet[i]], vertices[facet[(i + 1) % len(facet)]])
  588. value += distance_to_side(x0, side, hp_param[0]) * \
  589. lineseg_integrate(facet, i, side, expr, degree)
  590. return value
  591. def gradient_terms(binomial_power=0, no_of_gens=2):
  592. """Returns a list of all the possible monomials between
  593. 0 and y**binomial_power for 2D case and z**binomial_power
  594. for 3D case.
  595. Parameters
  596. ==========
  597. binomial_power :
  598. Power upto which terms are generated.
  599. no_of_gens :
  600. Denotes whether terms are being generated for 2D or 3D case.
  601. Examples
  602. ========
  603. >>> from sympy.integrals.intpoly import gradient_terms
  604. >>> gradient_terms(2)
  605. [[1, 0, 0, 0], [y, 0, 1, 0], [y**2, 0, 2, 0], [x, 1, 0, 0],
  606. [x*y, 1, 1, 0], [x**2, 2, 0, 0]]
  607. >>> gradient_terms(2, 3)
  608. [[[[1, 0, 0, 0, 0, 0, 0, 0]]], [[[y, 0, 1, 0, 1, 0, 0, 0],
  609. [z, 0, 0, 1, 1, 0, 1, 0]], [[x, 1, 0, 0, 1, 1, 0, 0]]],
  610. [[[y**2, 0, 2, 0, 2, 0, 0, 0], [y*z, 0, 1, 1, 2, 0, 1, 0],
  611. [z**2, 0, 0, 2, 2, 0, 2, 0]], [[x*y, 1, 1, 0, 2, 1, 0, 0],
  612. [x*z, 1, 0, 1, 2, 1, 1, 0]], [[x**2, 2, 0, 0, 2, 2, 0, 0]]]]
  613. """
  614. if no_of_gens == 2:
  615. count = 0
  616. terms = [None] * int((binomial_power ** 2 + 3 * binomial_power + 2) / 2)
  617. for x_count in range(0, binomial_power + 1):
  618. for y_count in range(0, binomial_power - x_count + 1):
  619. terms[count] = [x**x_count*y**y_count,
  620. x_count, y_count, 0]
  621. count += 1
  622. else:
  623. terms = [[[[x ** x_count * y ** y_count *
  624. z ** (z_count - y_count - x_count),
  625. x_count, y_count, z_count - y_count - x_count,
  626. z_count, x_count, z_count - y_count - x_count, 0]
  627. for y_count in range(z_count - x_count, -1, -1)]
  628. for x_count in range(0, z_count + 1)]
  629. for z_count in range(0, binomial_power + 1)]
  630. return terms
  631. def hyperplane_parameters(poly, vertices=None):
  632. """A helper function to return the hyperplane parameters
  633. of which the facets of the polytope are a part of.
  634. Parameters
  635. ==========
  636. poly :
  637. The input 2/3-Polytope.
  638. vertices :
  639. Vertex indices of 3-Polytope.
  640. Examples
  641. ========
  642. >>> from sympy import Point, Polygon
  643. >>> from sympy.integrals.intpoly import hyperplane_parameters
  644. >>> hyperplane_parameters(Polygon(Point(0, 3), Point(5, 3), Point(1, 1)))
  645. [((0, 1), 3), ((1, -2), -1), ((-2, -1), -3)]
  646. >>> cube = [[(0, 0, 0), (0, 0, 5), (0, 5, 0), (0, 5, 5), (5, 0, 0),\
  647. (5, 0, 5), (5, 5, 0), (5, 5, 5)],\
  648. [2, 6, 7, 3], [3, 7, 5, 1], [7, 6, 4, 5], [1, 5, 4, 0],\
  649. [3, 1, 0, 2], [0, 4, 6, 2]]
  650. >>> hyperplane_parameters(cube[1:], cube[0])
  651. [([0, -1, 0], -5), ([0, 0, -1], -5), ([-1, 0, 0], -5),
  652. ([0, 1, 0], 0), ([1, 0, 0], 0), ([0, 0, 1], 0)]
  653. """
  654. if isinstance(poly, Polygon):
  655. vertices = list(poly.vertices) + [poly.vertices[0]] # Close the polygon
  656. params = [None] * (len(vertices) - 1)
  657. for i in range(len(vertices) - 1):
  658. v1 = vertices[i]
  659. v2 = vertices[i + 1]
  660. a1 = v1[1] - v2[1]
  661. a2 = v2[0] - v1[0]
  662. b = v2[0] * v1[1] - v2[1] * v1[0]
  663. factor = gcd_list([a1, a2, b])
  664. b = S(b) / factor
  665. a = (S(a1) / factor, S(a2) / factor)
  666. params[i] = (a, b)
  667. else:
  668. params = [None] * len(poly)
  669. for i, polygon in enumerate(poly):
  670. v1, v2, v3 = [vertices[vertex] for vertex in polygon[:3]]
  671. normal = cross_product(v1, v2, v3)
  672. b = sum([normal[j] * v1[j] for j in range(0, 3)])
  673. fac = gcd_list(normal)
  674. if fac.is_zero:
  675. fac = 1
  676. normal = [j / fac for j in normal]
  677. b = b / fac
  678. params[i] = (normal, b)
  679. return params
  680. def cross_product(v1, v2, v3):
  681. """Returns the cross-product of vectors (v2 - v1) and (v3 - v1)
  682. That is : (v2 - v1) X (v3 - v1)
  683. """
  684. v2 = [v2[j] - v1[j] for j in range(0, 3)]
  685. v3 = [v3[j] - v1[j] for j in range(0, 3)]
  686. return [v3[2] * v2[1] - v3[1] * v2[2],
  687. v3[0] * v2[2] - v3[2] * v2[0],
  688. v3[1] * v2[0] - v3[0] * v2[1]]
  689. def best_origin(a, b, lineseg, expr):
  690. """Helper method for polytope_integrate. Currently not used in the main
  691. algorithm.
  692. Explanation
  693. ===========
  694. Returns a point on the lineseg whose vector inner product with the
  695. divergence of `expr` yields an expression with the least maximum
  696. total power.
  697. Parameters
  698. ==========
  699. a :
  700. Hyperplane parameter denoting direction.
  701. b :
  702. Hyperplane parameter denoting distance.
  703. lineseg :
  704. Line segment on which to find the origin.
  705. expr :
  706. The expression which determines the best point.
  707. Algorithm(currently works only for 2D use case)
  708. ===============================================
  709. 1 > Firstly, check for edge cases. Here that would refer to vertical
  710. or horizontal lines.
  711. 2 > If input expression is a polynomial containing more than one generator
  712. then find out the total power of each of the generators.
  713. x**2 + 3 + x*y + x**4*y**5 ---> {x: 7, y: 6}
  714. If expression is a constant value then pick the first boundary point
  715. of the line segment.
  716. 3 > First check if a point exists on the line segment where the value of
  717. the highest power generator becomes 0. If not check if the value of
  718. the next highest becomes 0. If none becomes 0 within line segment
  719. constraints then pick the first boundary point of the line segment.
  720. Actually, any point lying on the segment can be picked as best origin
  721. in the last case.
  722. Examples
  723. ========
  724. >>> from sympy.integrals.intpoly import best_origin
  725. >>> from sympy.abc import x, y
  726. >>> from sympy import Point, Segment2D
  727. >>> l = Segment2D(Point(0, 3), Point(1, 1))
  728. >>> expr = x**3*y**7
  729. >>> best_origin((2, 1), 3, l, expr)
  730. (0, 3.0)
  731. """
  732. a1, b1 = lineseg.points[0]
  733. def x_axis_cut(ls):
  734. """Returns the point where the input line segment
  735. intersects the x-axis.
  736. Parameters
  737. ==========
  738. ls :
  739. Line segment
  740. """
  741. p, q = ls.points
  742. if p.y.is_zero:
  743. return tuple(p)
  744. elif q.y.is_zero:
  745. return tuple(q)
  746. elif p.y/q.y < S.Zero:
  747. return p.y * (p.x - q.x)/(q.y - p.y) + p.x, S.Zero
  748. else:
  749. return ()
  750. def y_axis_cut(ls):
  751. """Returns the point where the input line segment
  752. intersects the y-axis.
  753. Parameters
  754. ==========
  755. ls :
  756. Line segment
  757. """
  758. p, q = ls.points
  759. if p.x.is_zero:
  760. return tuple(p)
  761. elif q.x.is_zero:
  762. return tuple(q)
  763. elif p.x/q.x < S.Zero:
  764. return S.Zero, p.x * (p.y - q.y)/(q.x - p.x) + p.y
  765. else:
  766. return ()
  767. gens = (x, y)
  768. power_gens = {}
  769. for i in gens:
  770. power_gens[i] = S.Zero
  771. if len(gens) > 1:
  772. # Special case for vertical and horizontal lines
  773. if len(gens) == 2:
  774. if a[0] == 0:
  775. if y_axis_cut(lineseg):
  776. return S.Zero, b/a[1]
  777. else:
  778. return a1, b1
  779. elif a[1] == 0:
  780. if x_axis_cut(lineseg):
  781. return b/a[0], S.Zero
  782. else:
  783. return a1, b1
  784. if isinstance(expr, Expr): # Find the sum total of power of each
  785. if expr.is_Add: # generator and store in a dictionary.
  786. for monomial in expr.args:
  787. if monomial.is_Pow:
  788. if monomial.args[0] in gens:
  789. power_gens[monomial.args[0]] += monomial.args[1]
  790. else:
  791. for univariate in monomial.args:
  792. term_type = len(univariate.args)
  793. if term_type == 0 and univariate in gens:
  794. power_gens[univariate] += 1
  795. elif term_type == 2 and univariate.args[0] in gens:
  796. power_gens[univariate.args[0]] +=\
  797. univariate.args[1]
  798. elif expr.is_Mul:
  799. for term in expr.args:
  800. term_type = len(term.args)
  801. if term_type == 0 and term in gens:
  802. power_gens[term] += 1
  803. elif term_type == 2 and term.args[0] in gens:
  804. power_gens[term.args[0]] += term.args[1]
  805. elif expr.is_Pow:
  806. power_gens[expr.args[0]] = expr.args[1]
  807. elif expr.is_Symbol:
  808. power_gens[expr] += 1
  809. else: # If `expr` is a constant take first vertex of the line segment.
  810. return a1, b1
  811. # TODO : This part is quite hacky. Should be made more robust with
  812. # TODO : respect to symbol names and scalable w.r.t higher dimensions.
  813. power_gens = sorted(power_gens.items(), key=lambda k: str(k[0]))
  814. if power_gens[0][1] >= power_gens[1][1]:
  815. if y_axis_cut(lineseg):
  816. x0 = (S.Zero, b / a[1])
  817. elif x_axis_cut(lineseg):
  818. x0 = (b / a[0], S.Zero)
  819. else:
  820. x0 = (a1, b1)
  821. else:
  822. if x_axis_cut(lineseg):
  823. x0 = (b/a[0], S.Zero)
  824. elif y_axis_cut(lineseg):
  825. x0 = (S.Zero, b/a[1])
  826. else:
  827. x0 = (a1, b1)
  828. else:
  829. x0 = (b/a[0])
  830. return x0
  831. def decompose(expr, separate=False):
  832. """Decomposes an input polynomial into homogeneous ones of
  833. smaller or equal degree.
  834. Explanation
  835. ===========
  836. Returns a dictionary with keys as the degree of the smaller
  837. constituting polynomials. Values are the constituting polynomials.
  838. Parameters
  839. ==========
  840. expr : Expr
  841. Polynomial(SymPy expression).
  842. separate : bool
  843. If True then simply return a list of the constituent monomials
  844. If not then break up the polynomial into constituent homogeneous
  845. polynomials.
  846. Examples
  847. ========
  848. >>> from sympy.abc import x, y
  849. >>> from sympy.integrals.intpoly import decompose
  850. >>> decompose(x**2 + x*y + x + y + x**3*y**2 + y**5)
  851. {1: x + y, 2: x**2 + x*y, 5: x**3*y**2 + y**5}
  852. >>> decompose(x**2 + x*y + x + y + x**3*y**2 + y**5, True)
  853. {x, x**2, y, y**5, x*y, x**3*y**2}
  854. """
  855. poly_dict = {}
  856. if isinstance(expr, Expr) and not expr.is_number:
  857. if expr.is_Symbol:
  858. poly_dict[1] = expr
  859. elif expr.is_Add:
  860. symbols = expr.atoms(Symbol)
  861. degrees = [(sum(degree_list(monom, *symbols)), monom)
  862. for monom in expr.args]
  863. if separate:
  864. return {monom[1] for monom in degrees}
  865. else:
  866. for monom in degrees:
  867. degree, term = monom
  868. if poly_dict.get(degree):
  869. poly_dict[degree] += term
  870. else:
  871. poly_dict[degree] = term
  872. elif expr.is_Pow:
  873. _, degree = expr.args
  874. poly_dict[degree] = expr
  875. else: # Now expr can only be of `Mul` type
  876. degree = 0
  877. for term in expr.args:
  878. term_type = len(term.args)
  879. if term_type == 0 and term.is_Symbol:
  880. degree += 1
  881. elif term_type == 2:
  882. degree += term.args[1]
  883. poly_dict[degree] = expr
  884. else:
  885. poly_dict[0] = expr
  886. if separate:
  887. return set(poly_dict.values())
  888. return poly_dict
  889. def point_sort(poly, normal=None, clockwise=True):
  890. """Returns the same polygon with points sorted in clockwise or
  891. anti-clockwise order.
  892. Note that it's necessary for input points to be sorted in some order
  893. (clockwise or anti-clockwise) for the integration algorithm to work.
  894. As a convention algorithm has been implemented keeping clockwise
  895. orientation in mind.
  896. Parameters
  897. ==========
  898. poly:
  899. 2D or 3D Polygon.
  900. normal : optional
  901. The normal of the plane which the 3-Polytope is a part of.
  902. clockwise : bool, optional
  903. Returns points sorted in clockwise order if True and
  904. anti-clockwise if False.
  905. Examples
  906. ========
  907. >>> from sympy.integrals.intpoly import point_sort
  908. >>> from sympy import Point
  909. >>> point_sort([Point(0, 0), Point(1, 0), Point(1, 1)])
  910. [Point2D(1, 1), Point2D(1, 0), Point2D(0, 0)]
  911. """
  912. pts = poly.vertices if isinstance(poly, Polygon) else poly
  913. n = len(pts)
  914. if n < 2:
  915. return list(pts)
  916. order = S.One if clockwise else S.NegativeOne
  917. dim = len(pts[0])
  918. if dim == 2:
  919. center = Point(sum(map(lambda vertex: vertex.x, pts)) / n,
  920. sum(map(lambda vertex: vertex.y, pts)) / n)
  921. else:
  922. center = Point(sum(map(lambda vertex: vertex.x, pts)) / n,
  923. sum(map(lambda vertex: vertex.y, pts)) / n,
  924. sum(map(lambda vertex: vertex.z, pts)) / n)
  925. def compare(a, b):
  926. if a.x - center.x >= S.Zero and b.x - center.x < S.Zero:
  927. return -order
  928. elif a.x - center.x < 0 and b.x - center.x >= 0:
  929. return order
  930. elif a.x - center.x == 0 and b.x - center.x == 0:
  931. if a.y - center.y >= 0 or b.y - center.y >= 0:
  932. return -order if a.y > b.y else order
  933. return -order if b.y > a.y else order
  934. det = (a.x - center.x) * (b.y - center.y) -\
  935. (b.x - center.x) * (a.y - center.y)
  936. if det < 0:
  937. return -order
  938. elif det > 0:
  939. return order
  940. first = (a.x - center.x) * (a.x - center.x) +\
  941. (a.y - center.y) * (a.y - center.y)
  942. second = (b.x - center.x) * (b.x - center.x) +\
  943. (b.y - center.y) * (b.y - center.y)
  944. return -order if first > second else order
  945. def compare3d(a, b):
  946. det = cross_product(center, a, b)
  947. dot_product = sum([det[i] * normal[i] for i in range(0, 3)])
  948. if dot_product < 0:
  949. return -order
  950. elif dot_product > 0:
  951. return order
  952. return sorted(pts, key=cmp_to_key(compare if dim==2 else compare3d))
  953. def norm(point):
  954. """Returns the Euclidean norm of a point from origin.
  955. Parameters
  956. ==========
  957. point:
  958. This denotes a point in the dimension_al spac_e.
  959. Examples
  960. ========
  961. >>> from sympy.integrals.intpoly import norm
  962. >>> from sympy import Point
  963. >>> norm(Point(2, 7))
  964. sqrt(53)
  965. """
  966. half = S.Half
  967. if isinstance(point, (list, tuple)):
  968. return sum([coord ** 2 for coord in point]) ** half
  969. elif isinstance(point, Point):
  970. if isinstance(point, Point2D):
  971. return (point.x ** 2 + point.y ** 2) ** half
  972. else:
  973. return (point.x ** 2 + point.y ** 2 + point.z) ** half
  974. elif isinstance(point, dict):
  975. return sum(i**2 for i in point.values()) ** half
  976. def intersection(geom_1, geom_2, intersection_type):
  977. """Returns intersection between geometric objects.
  978. Explanation
  979. ===========
  980. Note that this function is meant for use in integration_reduction and
  981. at that point in the calling function the lines denoted by the segments
  982. surely intersect within segment boundaries. Coincident lines are taken
  983. to be non-intersecting. Also, the hyperplane intersection for 2D case is
  984. also implemented.
  985. Parameters
  986. ==========
  987. geom_1, geom_2:
  988. The input line segments.
  989. Examples
  990. ========
  991. >>> from sympy.integrals.intpoly import intersection
  992. >>> from sympy import Point, Segment2D
  993. >>> l1 = Segment2D(Point(1, 1), Point(3, 5))
  994. >>> l2 = Segment2D(Point(2, 0), Point(2, 5))
  995. >>> intersection(l1, l2, "segment2D")
  996. (2, 3)
  997. >>> p1 = ((-1, 0), 0)
  998. >>> p2 = ((0, 1), 1)
  999. >>> intersection(p1, p2, "plane2D")
  1000. (0, 1)
  1001. """
  1002. if intersection_type[:-2] == "segment":
  1003. if intersection_type == "segment2D":
  1004. x1, y1 = geom_1.points[0]
  1005. x2, y2 = geom_1.points[1]
  1006. x3, y3 = geom_2.points[0]
  1007. x4, y4 = geom_2.points[1]
  1008. elif intersection_type == "segment3D":
  1009. x1, y1, z1 = geom_1.points[0]
  1010. x2, y2, z2 = geom_1.points[1]
  1011. x3, y3, z3 = geom_2.points[0]
  1012. x4, y4, z4 = geom_2.points[1]
  1013. denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
  1014. if denom:
  1015. t1 = x1 * y2 - y1 * x2
  1016. t2 = x3 * y4 - x4 * y3
  1017. return (S(t1 * (x3 - x4) - t2 * (x1 - x2)) / denom,
  1018. S(t1 * (y3 - y4) - t2 * (y1 - y2)) / denom)
  1019. if intersection_type[:-2] == "plane":
  1020. if intersection_type == "plane2D": # Intersection of hyperplanes
  1021. a1x, a1y = geom_1[0]
  1022. a2x, a2y = geom_2[0]
  1023. b1, b2 = geom_1[1], geom_2[1]
  1024. denom = a1x * a2y - a2x * a1y
  1025. if denom:
  1026. return (S(b1 * a2y - b2 * a1y) / denom,
  1027. S(b2 * a1x - b1 * a2x) / denom)
  1028. def is_vertex(ent):
  1029. """If the input entity is a vertex return True.
  1030. Parameter
  1031. =========
  1032. ent :
  1033. Denotes a geometric entity representing a point.
  1034. Examples
  1035. ========
  1036. >>> from sympy import Point
  1037. >>> from sympy.integrals.intpoly import is_vertex
  1038. >>> is_vertex((2, 3))
  1039. True
  1040. >>> is_vertex((2, 3, 6))
  1041. True
  1042. >>> is_vertex(Point(2, 3))
  1043. True
  1044. """
  1045. if isinstance(ent, tuple):
  1046. if len(ent) in [2, 3]:
  1047. return True
  1048. elif isinstance(ent, Point):
  1049. return True
  1050. return False
  1051. def plot_polytope(poly):
  1052. """Plots the 2D polytope using the functions written in plotting
  1053. module which in turn uses matplotlib backend.
  1054. Parameter
  1055. =========
  1056. poly:
  1057. Denotes a 2-Polytope.
  1058. """
  1059. from sympy.plotting.plot import Plot, List2DSeries
  1060. xl = list(map(lambda vertex: vertex.x, poly.vertices))
  1061. yl = list(map(lambda vertex: vertex.y, poly.vertices))
  1062. xl.append(poly.vertices[0].x) # Closing the polygon
  1063. yl.append(poly.vertices[0].y)
  1064. l2ds = List2DSeries(xl, yl)
  1065. p = Plot(l2ds, axes='label_axes=True')
  1066. p.show()
  1067. def plot_polynomial(expr):
  1068. """Plots the polynomial using the functions written in
  1069. plotting module which in turn uses matplotlib backend.
  1070. Parameter
  1071. =========
  1072. expr:
  1073. Denotes a polynomial(SymPy expression).
  1074. """
  1075. from sympy.plotting.plot import plot3d, plot
  1076. gens = expr.free_symbols
  1077. if len(gens) == 2:
  1078. plot3d(expr)
  1079. else:
  1080. plot(expr)