Repository: sympy/sympy
Pull request: Integration of Polygon gives no len() error solved
<!-- Your title above should be a short description of what
was changed. Do not include the issue number in the title. -->
Fixes #15843 
#### References to other Issues or PRs
<!-- If this pull request fixes an issue, write "Fixes #NNNN" in that exact
format, e.g. "Fixes #1234". See
https://github.com/blog/1506-closing-issues-via-pull-requests . Please also
write a comment on that issue linking back to this pull request once it is
open. -->

Integrating a function over a polygon(in intergrals/intpoly.py) it gives no method len() error. Also some more changes are made as object of Polygon() was passes as poly but was used as a list at some positions which gave error. Those are also resolved.
#### Brief description of what is fixed or changed
Earlier the output was.
![screenshot from 2019-01-26 03-12-14](https://user-images.githubusercontent.com/40005050/51774631-08613c80-2119-11e9-9b9d-2473146020bc.png)

After changing output is
![screenshot from 2019-01-26 03-13-38](https://user-images.githubusercontent.com/40005050/51774659-19aa4900-2119-11e9-9a41-8995b24d22e3.png)
 


#### Other comments


#### Release Notes

<!-- Write the release notes for this release below. See
https://github.com/sympy/sympy/wiki/Writing-Release-Notes for more information
on how to write release notes. The bot will check your release notes
automatically to see if they are formatted correctly. -->

<!-- BEGIN RELEASE NOTES -->

- integrals

   - Added functionality to pass both Polygon type object and a list to point_sort in intpoly.py

<!-- END RELEASE NOTES -->


Problem statement:
`polytope_integrate`: Polygon vertex order assumption
The `Polygon` class has a positive area if the vertices are in ccw order. In contrast the default behaviour of `polytope_integrate` is given the same polygon that the integral of, for example, the one function is negative.

This inconsistent behaviour is especially noticeable because the `polytope_integrate` docs refer to the `Polygon` class docs which state "ccw => positive area". Only the API section gives an implicit statement that cw order is assumed.

In addition the reordering algorithm is apparently broken on debian 9 + python3.5.3 + sympy1.3
```
Traceback (most recent call last):
  File "/home/xxx/yyy/jump2d.py", line 41, in <module>
    B = simplify(polytope_integrate(_D, p0, clockwise=True))
  File "/home/xxx/.local/lib/python3.5/site-packages/sympy/integrals/intpoly.py", line 67, in polytope_integrate
    poly = point_sort(poly)
  File "/home/xxx/.local/lib/python3.5/site-packages/sympy/integrals/intpoly.py", line 1012, in point_sort
    n = len(poly)
TypeError: object of type 'Polygon' has no len()
```

Hints from issue discussion:
I will be looking up on the issue.
@jgersti Can you please share the code of what were you trying to do. Especially the arguments passed.
Here is the condensed source file which shows the second issue. The expected output is three times `1`.
[issue15843.py.txt](https://github.com/sympy/sympy/files/2796186/issue15843.py.txt)

(The first output might be because of other issues with the integration algotithm itself.)

Commit message: Added test cases and added functionality to pass both list and Polygon object

Patch to review:
diff --git a/sympy/integrals/intpoly.py b/sympy/integrals/intpoly.py
index 7aa41207acba..0cc60649f60e 100644
--- a/sympy/integrals/intpoly.py
+++ b/sympy/integrals/intpoly.py
@@ -1009,19 +1009,26 @@ def point_sort(poly, normal=None, clockwise=True):
     >>> point_sort([Point(0, 0), Point(1, 0), Point(1, 1)])
     [Point2D(1, 1), Point2D(1, 0), Point2D(0, 0)]
     """
-    n = len(poly)
+    flag = 0
+    if(isinstance(poly, Polygon)):
+        n = len(poly.vertices)
+    else:
+        flag = 1
+        n = len(poly)
+        poly = Polygon(*poly)
+
     if n < 2:
         return poly
 
     order = S(1) if clockwise else S(-1)
-    dim = len(poly[0])
+    dim = len(poly.vertices[0])
     if dim == 2:
-        center = Point(sum(map(lambda vertex: vertex.x, poly)) / n,
-                       sum(map(lambda vertex: vertex.y, poly)) / n)
+        center = Point(sum(map(lambda vertex: vertex.x, poly.vertices)) / n,
+                        sum(map(lambda vertex: vertex.y, poly.vertices)) / n)
     else:
-        center = Point(sum(map(lambda vertex: vertex.x, poly)) / n,
-                       sum(map(lambda vertex: vertex.y, poly)) / n,
-                       sum(map(lambda vertex: vertex.z, poly)) / n)
+        center = Point(sum(map(lambda vertex: vertex.x, poly.vertices)) / n,
+                        sum(map(lambda vertex: vertex.y, poly.vertices)) / n,
+                        sum(map(lambda vertex: vertex.z, poly.vertices)) / n)
 
     def compare(a, b):
         if a.x - center.x >= S.Zero and b.x - center.x < S.Zero:
@@ -1055,8 +1062,14 @@ def compare3d(a, b):
             return order
 
     if dim == 2:
-        return sorted(poly, key=cmp_to_key(compare))
-    return sorted(poly, key=cmp_to_key(compare3d))
+        if flag == 0:
+            return Polygon(*sorted(poly.vertices, key=cmp_to_key(compare)))
+        else:
+            return sorted(poly.vertices, key=cmp_to_key(compare))
+    if flag == 0:
+        return Polygon(*sorted(poly.vertices, key=cmp_to_key(compare3d)))
+    else:
+        return sorted(poly.vertices, key=cmp_to_key(compare3d))
 
 
 def norm(point):
diff --git a/sympy/integrals/tests/test_intpoly.py b/sympy/integrals/tests/test_intpoly.py
index 01b00f171c03..02c0611313cb 100644
--- a/sympy/integrals/tests/test_intpoly.py
+++ b/sympy/integrals/tests/test_intpoly.py
@@ -3,11 +3,11 @@
 from sympy.core import S
 
 from sympy.integrals.intpoly import (decompose, best_origin,
-                                     polytope_integrate)
+                                     polytope_integrate, point_sort)
 
 from sympy.geometry.line import Segment2D
 from sympy.geometry.polygon import Polygon
-from sympy.geometry.point import Point
+from sympy.geometry.point import Point, Point2D
 from sympy.abc import x, y, z
 
 
@@ -490,6 +490,14 @@ def test_polytope_integrate():
          y * z: 3125 / S(4), z ** 2: 3125 / S(3), y ** 2: 3125 / S(3),
          z: 625 / S(2), x * y: 3125 / S(4), x ** 2: 3125 / S(3)}
 
+def test_point_sort():
+    assert point_sort([Point(0, 0), Point(1, 0), Point(1, 1)]) == \
+        [Point2D(1, 1), Point2D(1, 0), Point2D(0, 0)]
+
+    fig6 = Polygon((0, 0), (1, 0), (1, 1))
+    assert polytope_integrate(fig6, x*y) == S(-1)/8
+    assert polytope_integrate(fig6, x*y, clockwise = True) == S(1)/8
+
 
 def test_polytopes_intersecting_sides():
     fig5 = Polygon(Point(-4.165, -0.832), Point(-3.668, 1.568),


Code review comments:
diff_hunk: @@ -1009,19 +1009,26 @@ def point_sort(poly, normal=None, clockwise=True):
     >>> point_sort([Point(0, 0), Point(1, 0), Point(1, 1)])
     [Point2D(1, 1), Point2D(1, 0), Point2D(0, 0)]
     """
-    n = len(poly)
+    flag = 0
path: sympy/integrals/intpoly.py
text: @user1:
I would recommend returning a list only. At the outset, convert Polygon to vertices instead of poly and then no flag will be needed and the code will use vertices instead of poly.vertices.

```python
if isinstance(poly, Polygon):
    pts = poly.vertices
else:
    pts = poly
n = len(pts)
...
future reference to poly.vertices becomes pts and final lines at 1064 are left unchanged except for changing poly to pts
```

@author:
@user1 I was using flag to differentiate when to return Polygon object and when to return a list.
Because when we pass a list of points, then it must return a list (as shown in line 1009 in integrals/intpoly.py) whereas when we set clockwise=True in polytope_integrate() then only point_sort() is called and must return a Polygon type object for further evaluation.