Jeff L
Jeff L

Reputation: 643

Find which two angles an angle is between in Python

I have a list of angles (in radians) in the range [-pi, pi]. The angles can be assumed to ordered as follows:

Now given a random angle in the [-pi, pi] range, I want to determine which two of the angles in the list the random angle is between. Example usage of this is_between function would be like so:

# doesn't work for angle == pi!
def is_between(angle, angles):
    for i in range(len(angles)):
        min = angles[i]
        max = angles[(i + 1) % len(angles)]
        if min <= angle <= max:
            return min, max
 

angles = [-pi/6, pi/4, 5*pi/6, -2*pi/3]
bounds = is_between(pi, angles)
print(bounds) # should print (5*pi/6, -2*pi/3)

My initial though would be to just iterate through the list, picking two adjacent angles, and check if the provided angle is between the two values, but this doesn't work in cases near pi. I've seen a solution similar to this in degrees, but I would like a solution that doesn't involve converting the units for the entire angles list as a prerequisite step.

Upvotes: 1

Views: 283

Answers (2)

Mostafa Nazari
Mostafa Nazari

Reputation: 716

each two angles create two arcs (small and big) on the unit circle, if we assume that the shortest arc is the distance between two angles, and all the angles are in [-pi, pi], then we can solve this problem like this:

from cmath import pi

eps = 0.00001

def arc_vector(a1, a2):
    result = a2 - a1
    if result > pi   : result -= 2*pi
    elif result <-pi : result += 2*pi
    return result

def is_between(angle, angles):
    for i in range(len(angles)):
        min = angles[i]
        max = angles[(i + 1) % len(angles)]
        arc1 = arc_vector(min, angle)
        arc2 = arc_vector(angle, max)
        arct = arc_vector(min, max)
        if abs(arc1 + arc2 - arct) < eps and arc1 * arc2 > 0: # means they are on the same arc and the angle is between them
            return min, max
 

angles = [-pi/6, pi/4, 5*pi/6, -2*pi/3]
bounds = is_between(pi, angles)
print(bounds) # should print (5*pi/6, -2*pi/3)

Upvotes: 0

BrokenBenchmark
BrokenBenchmark

Reputation: 19252

As described in a comment by the OP, the first element gives the starting point for the angle generation process. If we see an angle with a radian value that is smaller than the first element, we can normalize it by adding 2 * pi to it, and then we can perform our comparison operation as normal

For example, when given the bounds 5*pi/6 and -2*pi/3, we turn these inputs into 5*pi/6 and 4*pi/3, so that the lower bound is strictly less than the upper bound.

This implementation uses math.pi rather than np.pi. They're all the same constant, so this shouldn't affect correctness..

from math import pi

def is_between(angle, angles):
    if angle < angles[0]:
        angle += 2 * pi
        
    for i in range(len(angles)):
        if angles[0] <= angles[i]:
            lower_bound = angles[i]
        else:
            lower_bound = angles[i] + 2 * pi
        
        if angles[0] <= angles[(i + 1) % len(angles)]:
            upper_bound = angles[(i + 1) % len(angles)]
        else:
            upper_bound = angles[(i + 1) % len(angles)] + 2 * pi
        if lower_bound <= angle <= upper_bound:
            return angles[i], angles[(i + 1) % len(angles)]
            
    raise ValueError("Could not find bounds")

angles = [-pi/6, pi/4, 5*pi/6, -2*pi/3]
bounds = is_between(pi, angles)

This outputs:

(2.6179938779914944, -2.0943951023931953)

Upvotes: 0

Related Questions