dblume/pizza-slice

View on GitHub
pizza_slice.py

Summary

Maintainability
A
45 mins
Test Coverage
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function
import time
from argparse import ArgumentParser
import math

__author__ = 'David Blume'


def halve_slice(tip_angle, radius):
    """ Given the last pizza slice, tip down, divide it in half with
    a horizontal cut with the crust at the top, such that each half
    has the same total area.
       ____
    _--    --_
    \        /
     \------/
      \    /
       \  /
        \/
    :param tip_angle:
    :param radius:
    :return:
    """
    # area_of_slice = (math.pi * radius * radius) * (tip_angle / (2 * math.pi))
    area_of_half_slice = radius * radius * tip_angle / 4.0
    # area_of_half_slice = area_of_pizza / num_slices / 2.0
    # tip_angle = math.pi * 2 / num_slices  # in radians

    # Now we divide the triangular slice into two smaller right triangles.
    #
    # a = angle of right triangle at tip
    # h = hypotenuse (a fraction of the radius)
    # w = width of triangle
    # l = length of triangle
    # (l * w) / 2 = area of right triangle
    # (l * w) / 2 * 2 = area of half slice
    #
    #            w
    #   \-----|-----/
    #    \    |90  /
    #     \  l|   /
    #      \  |  / h
    #       \ |a/
    #        \|/
    #         v
    # area_of_half_slice =    l    *     w
    # area_of_half_slice = cos(a)h * sin(a)h
    # area_of_half_slice = (h * h)(cos(a) * sin(a))
    # area_of_half_slice / (cos(a) * sin(a)) = h * h
    a = tip_angle / 2.0
    h = math.sqrt(area_of_half_slice/(math.cos(a)*math.sin(a)))
    l = h * math.cos(a)
    return h, l


def calc_area_for_big_angle(angle, fraction_in_triangles, radius):
    """ Calculate the area for wide slices where fraction_in_triangles of angle
    is used in the triangles.

          /\-----------|-----------/\
         /   \         |90       /   \
        |      \      l|       /      |
        |      r \     |     / r      |
         ---.__    \   |a  /    __.---
            rx --.__ \ | /b__.-- ry     "angle" is angle from line segments rx to ry.
                    --.v.--

    If theta is minimum, 1.8956, then fraction_in_triangles will be around 0.9999655 for a good match
    If theta is maximum, pi, then fraction_in_trangles will be around 0.7352581 for a good match
    """
    angle_in_rect = angle * fraction_in_triangles
    a = angle_in_rect / 2.0  # angle in each triangle part
    area_of_rectangle_part = math.cos(a) * math.sin(a) * radius * radius
    angle_in_sector = angle - angle_in_rect
    area_of_sector_parts = radius * radius * angle_in_sector / 2.0
    return area_of_rectangle_part + area_of_sector_parts


def halve_slice_big_angle(angle, radius):
    """ angle must be > 1.8956 (where length is 0.5835 * radius) and < 3.1416 """
    assert(1.8956 <= angle <= math.pi)
    area_of_half_slice = radius * radius * angle / 4.0

    # if angle = 1.8956, fraction_in_triangles = 0.9999655
    # if angle = 3.1416, fraction_in_trangles = 0.7352581

    count = 0
    min_angle = 1.8956
    min_angle_trifract = 0.9999655
    max_angle = math.pi
    max_angle_trifract = 0.7352581
    fraction_in_triangles = min_angle_trifract - ((angle - min_angle) / (max_angle - min_angle) * (min_angle_trifract - max_angle_trifract))
    done = False

    while not done:
        area = calc_area_for_big_angle(angle, fraction_in_triangles, radius)
        diff_pct = (area_of_half_slice - area) / area_of_half_slice
        print("Loop %d: angle %1.2f, tried fraction %1.6f and got difference of %1.3f - %1.3f = %1.2f%%" % (count, angle, fraction_in_triangles, area_of_half_slice, area, diff_pct * 100))
        height_of_line = math.cos((angle * fraction_in_triangles) / 2) * radius  # l
        if abs(diff_pct) < 0.0005:
            print("Close enough! We're done.");
            done = True
            break
        if area_of_half_slice > area:
            # Need to increase area. So decrease the percentage that goes to triangles.
            # Drive fraction_in_triangles closer to the low value in max_angle_trifract.
            min_angle_trifract = fraction_in_triangles
        else:
            # Need to decrease the area. So increase the percentage that goes to triangles.
            # Drive fraction_in_triangles closer to the high value in min_angle_trifract.
            max_angle_trifract = fraction_in_triangles
        fraction_in_triangles = min_angle_trifract - ( min_angle_trifract - max_angle_trifract ) * 0.5
        # Set fraction_in_triangles proportionally to where angle
        # sits between last_angle_min an
        count += 1
        if count > 10:
            print("Ran too many loops. Exiting.")
            break
    return height_of_line


def frange(start, stop, step):
    """ returns a generator expression """
    return (x * step for x in range(int(start / step), int(stop / step)))


def main(debug):
    start_time = time.time()
    # for i in range(1, 110):
#    for i in frange(90, 110, 0.01):
#        h, l = halve_slice(math.radians(i), 1)
#        print("angle = %1.1f (%1.4f radians): hypotenuse=%1.4f length= %1.4f" % (i, math.radians(i), h, l))

    print("Line height for %1.4f is %1.4fin" % (1.8956, halve_slice_big_angle(1.8956, 1.0)));
    print("Line height for %1.4f is %1.4fin" % (2.0, halve_slice_big_angle(2.0, 1.0)));
    print("Line height for %1.4f is %1.4fin" % ((1.8956 + math.pi)/2, halve_slice_big_angle((1.8956 + math.pi)/2, 1.0)));
    print("Line height for %1.4f is %1.4fin" % (3.0, halve_slice_big_angle(3.0, 1.0)));
    print("Line height for %1.4f is %1.4fin" % (math.pi, halve_slice_big_angle(math.pi, 1.0)));

    print("Done.  That took %1.2fs." % (time.time() - start_time))


if __name__ == '__main__':
    parser = ArgumentParser(description='Just a sample script.')
    parser.add_argument('-d', '--debug', action='store_true')
    args = parser.parse_args()
    main(args.debug)