DEV Community

Triv Prasad
Triv Prasad Subscriber

Posted on

Area of intersecting circles

A few days ago, my daughters wanted to know how to find out the bounded area of two intersecting circles. This program is an attempt to teach them both Trigonometry as well as computer science. We started off doing only the basic condition when both circles play nice and intersect with the distance between centers parallel to the horizontal axis. Slowly, we extended the code to cater for other cases where the circles only touch at one point, may not touch at all or one circle is completely inside the other circle.

Basically, the input takes three parameters -> the distance between the centers of the two circles and the radii of the two circles in question. The common area is the output of the program.

Looking for ways to make this a more optimal program as I'm new to python.

import sys
import math

# Return the area of the smaller circle
def smallercirclearea(r1, r2):
    radsmaller = min(r1, r2)
    return math.pi * radsmaller * radsmaller

def calcAreaIntersectingCircles(d, rad1, rad2):
    rad1sqr = rad1 * rad1
    rad2sqr = rad2 * rad2

    #if the circle centers are the same
    if d == 0:
        print ('\nCircles have the same center. Intersecting area will be area of smaller circle')
        return smallercirclearea(rad1,rad2)

    angle1 = (rad1sqr + (d * d) - rad2sqr) / (2 * rad1 * d)
    angle2 = (rad2sqr + (d * d) - rad1sqr) / (2 * rad2 * d)

    # Check to see if the circles are overlapping
    if ((angle1 < 1 and angle1 >= -1) or (angle2 < 1 and angle2 >= -1)):
        theta1 = (math.acos(angle1) * 2)
        theta2 = (math.acos(angle2) * 2)

        area1 = (0.5 * theta2 * rad2sqr) - (0.5 * rad2sqr * math.sin(theta2))
        area2 = (0.5 * theta1 * rad1sqr) - (0.5 * rad1sqr * math.sin(theta1))

        return area1 + area2
    elif angle1 == 1 and angle2 == 1:
        print ('\nCircles touch at a single degenerate point and do not intersect\n')
        return 0
    elif angle1 < -1 or angle2 < -1:
        print('\nSmaller circle is completely inside the larger circle. Intersecting area will be area of smaller circle')
        return smallercirclearea(rad1,rad2)
    else:
        print ('\nImaginary touch points\n')
        return -1

#Read the distance and radii from the command line
if len(sys.argv) == 4:
    distance = int(sys.argv[1])
    radiusCircle1 = int(sys.argv[2])
    radiusCircle2 = int(sys.argv[3])
    if distance < 0 or radiusCircle1 < 0 or radiusCircle2 < 0:
        print('Values cannot be negative. Goodbye')
        exit(1)

    print('\nThe intersecting area of the two circles is', round(calcAreaIntersectingCircles(float(distance), float(radiusCircle1), float(radiusCircle2)), 2), 'square units\n')
else:
    print('\nNEED 3 VALUES IN THE INPUT!!\n')
    exit(1)
Enter fullscreen mode Exit fullscreen mode

Top comments (5)

Collapse
 
rpalo profile image
Ryan Palo • Edited

This is really cool! It's neat that you're doing this with your daughters. Hopefully, they liked it!

Since it's pretty much a straightforward one-off calculation, I don't think there's a whole lot of optimizing you can do. I do have a couple "this is how you'll see it a lot in Python code" kind of things, though.

Generally, in a Python script like this where your interface (argument processing, getting user input, etc.) is in the same file as your main logic, you'll see bottom section written like this:

if __name__ == '__main__':
    if len(sys.argv) != 4:
        print('\nNEED 3 VALUES IN THE INPUT!!\n')
        exit(1)

    distance = float(sys.argv[1])
    radius1 = float(sys.argv[2])
    radius2 = float(sys.argv[3])
    if distance < 0 or radius1 < 0 or radius2 < 0:
        print('Values cannot be negative. Goodbye')
        exit(1)

    area = calcAreaIntersectingCircles(distance, radius1, radius2)

    print('\nThe intersecting area of the two circles is', round(area, 2), 'square units\n')

Python modules get a __name__ attribute when they're run. If they're called directly (a la python gimme-that-area.py), the __name__ attribute is set to __main__. If you import them, (like import gimme-that-area), the __name__ is set to "gimme-that-area". So this way, it allows you to import your code and use your calcAreaIntersectingCircles function in other places without erroring out due to missing command line args.

Other than that, if you want to get a feel for traditional Python style, you should have a read through PEP8, the main part of Python's style guide. You can run your code through this checker for some automatic feedback. Once you get comfortable installing packages with pip, try out pep8 or, my favorite, flake8.

If you ever have any questions about Python, feel free to reach out to me. I'm always happy to answer questions or help out.

Collapse
 
bacchu profile image
Triv Prasad

Very helpful. Will check out the style guide. And will definitely reach out for any questions. Thank you

Collapse
 
moopet profile image
Ben Sinclair • Edited

No real comments on making it more optimal, but I'd press them for some code cleanliness.

I'd consolidate my variable names if I were you. You use r1, rad1, radiusCircle1, rad1sqr and radius - and d and distance for that matter. I'd not be able to guess whether "d" meant distance or diameter without inspecting the code further.

The position of the numbers in your variable names is inconsistent meaning that it takes me a second longer to parse between rad1sqr (lowercase, number in middle) and radiusCircle1 (camelCase, number at end)

You switch between lowercase and camelCase for function names, sometimes abbreviating parts of compound names ("calc") and sometimes opting for the more verbose.

I'd probably make the first function shorter:

def smallercirclearea(r1, r2):
    return math.pi * (min(r1, r2) ** 2)


`
because the additional variable doesn't really help with readability, and this replacement follows the familiar pi - r - squared more closely. In fact, I'd be tempted to split it into

python
def circleArea(radius):
"""Calculate the area of a circle from its radius."""
return math.pi * (radius ** 2)
``
and call it with

python
circleArea(min(r1, r2))
``

when you need the smaller area, so that everything is a little more general and it can be used to get the larger area without having to duplicate the function. I've replaced the comment with a docstring so you can inspect it with other tools - in the simplest case in a debugger you can ask for "help circleArea" and it will come back with that message.

You cast the program's arguments to integers but the only place you use them you re-cast them to floats. You don't need to do this; either ints or floats will work fine.

I'd also suggest putting the main code inside a main function, which isn't absolutely required but it's traditional.

Collapse
 
bacchu profile image
Triv Prasad

Thank you. Appreciate the detailed feedback.

Collapse
 
bacchu profile image
Triv Prasad

I've made the changes suggested. Thanks everyone!