How to generate random closed shape and draw a line bisecting it

Similar to this question, except I also want a single line bisecting the shape in both area and perimeter. So the dream is to end up with a shape like the ones shown here, and a single line through the shape, where the area of the shape to one side of the line is equal to the other and ditto for perimeter.

I don't even know if this is possible or not. How would one even begin to compute the area/perimeter of the shape?

• If the area and the perimeter are bisected then surely the shape would be symmetrical (either by rotation or reflection)? If that's what you want then could you draw half the shape, then draw a copy rotated or reflected? – Thruston Nov 12 '13 at 19:07
• The idea is to show that for any arbitrary closed shape, not necessarily reflected or rotated, there exists a line that bisects both the area and perimeter. I have the proof; I just need a good picture. I don't want to draw something by hand. – user40934 Nov 12 '13 at 19:18
• You need something like solve in METAPOST then. – Thruston Nov 12 '13 at 19:26
• If you want just one illustration for your proof, consider inkscape. Find or create a good "random" figure, then locate the bisecting line "by hand" for this figure. Export to TeX. – Ethan Bolker Nov 12 '13 at 20:03
• I don't think I'm capable of accurately doing that by hand. – user40934 Nov 12 '13 at 20:09

I think this is a case where it might be wise to use an external tool for calculating the coordinates.

You can use Pythons shapely library together with the scipy.optimize tools for finding a split line:

import numpy as np
from shapely.geometry import Polygon, LineString
from shapely.ops import polygonize
import scipy.optimize

### Gaussian smoother from http://www.swharden.com/blog/2008-11-17-linear-data-smoothing-in-python/ for getting a nice polygon

def smoothListGaussian(list,degree=5):
window=degree*2-1
weight=np.array([1.0]*window)
weightGauss=[]
for i in range(window):
i=i-degree+1
frac=i/float(window)
gauss=1/(np.exp((4*(frac))**2))
weightGauss.append(gauss)
weight=np.array(weightGauss)*weight
smoothed=[0.0]*(len(list)-window)
for i in range(len(smoothed)):
smoothed[i]=sum(np.array(list[i:i+window])*weight)/sum(weight)
return smoothed

# Generate the polygon
theta = np.linspace(0,2*np.pi,200, endpoint=False)
r = np.random.lognormal(0,0.4,200)

r = smoothListGaussian(r, degree=10)

coords = zip(np.cos(theta)*r, np.sin(theta)*r)

polygon = Polygon(coords)

# The function for splitting the polygon and calculating the objective function value
def splitPoly(poly, parameters):
rot = parameters[0]
shift = parameters[1]

c = poly.centroid.coords[0]
l = polygon.length
shiftvec = (np.cos(rot), np.sin(rot))

linestart = (c[0] - shiftvec[0]*l + shiftvec[1]*shift, c[1] - shiftvec[1]*l - shiftvec[0]*shift)
lineend = (c[0] + shiftvec[0]*l + shiftvec[1]*shift, c[1] + shiftvec[1]*l - shiftvec[0]*shift)

line = LineString([linestart, lineend])

splitpoly = list(polygonize(poly.boundary.union( line ) ))
lengthdiff = 1+np.abs(splitpoly[0].length - splitpoly[1].length)

def wrapper(parameters):
return splitPoly(polygon, parameters)[0]

# Use a grid search for finding a good starting value
res = scipy.optimize.brute(wrapper, ((0.0,2),(-0.1,0.1)))

# Nelder-Mead for optimizing the parameters

# Split the polygon using the final parameter values
value, splitpoly = splitPoly(polygon, res.x)

print "Areas: ", splitpoly[0].area, splitpoly[1].area
print "Perimeters: ", splitpoly[0].length, splitpoly[1].length

# Write the coordinates
np.savetxt('polygonA.txt', np.array(list(splitpoly[0].exterior.coords)))
np.savetxt('polygonB.txt', np.array(list(splitpoly[1].exterior.coords)))

The polygons can then be plotted using PGFPlots:

\documentclass{standalone}
\usepackage{pgfplots}

\begin{document}
\begin{tikzpicture}
\begin{axis}[axis equal image, hide axis]
\addplot [no markers, fill=yellow] table {polygonA.txt};
\addplot [no markers, fill=orange] table {polygonB.txt};
\end{axis}
\end{tikzpicture}
\end{document}

• Thank you very much. I'm assuming this would work with python.sty. – user40934 Nov 13 '13 at 3:38
• +1 for this answer. In fact, in my opinion most of the questions related to “random points”, “random paths”, “amoebas“, etc., shouldn't be done in LaTeX. You calculate them once in an external tool, an then load it in LaTeX. It's not neccessary to randomly recalculate in each compilation. In my case I do them in MATLAB, but, like this, the important part is done outside LaTeX. – Manuel Nov 17 '13 at 20:59