1

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

  • every increasing index in the list will be counter-clockwise to the previous
  • there are no duplicates in the list
  • the values in the list will never surpass a full 2*pi rotation from the first value in the list

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.

Jeff L
  • 491
  • 5
  • 14
  • @AbhyudayVaish You can probably assume that there's a (missing) `from math import pi`. On the other hand, what are the constraints on the input? – BrokenBenchmark Apr 04 '22 at 04:36
  • I mentioned in the OP that the input angle is between [-pi, pi], and that `angles` is a list of angles between [-pi, pi] that are in a counterclockwise direction. What is not constrained? – Jeff L Apr 04 '22 at 04:38
  • @AbhyudayVaish I left it out because I felt it's not important but you can assume `pi` is `numpy.pi` – Jeff L Apr 04 '22 at 04:40
  • @AbhyudayVaish I'm not sure what you mean.. `pi/6` would be the radian equivalent of 30 degrees.. – Jeff L Apr 04 '22 at 04:44
  • How are the elements ordered? What values can the elements take on? What should we do when we see two elements where the first is greater than the second? Are all the angles distinct? If not, how do you expect to handle duplicate angles? – BrokenBenchmark Apr 04 '22 at 04:44
  • @BrokenBenchmark to be extremely pedantic, pick any random point between `-pi` and `pi` radians. From that point, every incrementing index in the array will be a point with radian value between `-pi` to `pi` that is counter-clockwise from the initial point. There will never be duplicate values, and the angles in the list will never surpass one full rotation of the circle. i.e. starting at `-pi/6`, `pi/4` is counterclockwise to `-pi/6`, `5*pi/6` is counter-clockwise to `pi/4`. – Jeff L Apr 04 '22 at 04:48
  • @JeffL Just print `pi/6`, It would give you `0.523`. To get angles in radian use `math.degrees(pi/6)` – Abhyuday Vaish Apr 04 '22 at 04:49
  • @AbhyudayVaish, no, what you've written will convert `0.523` radians to degrees. As you can see, the result is 30 degrees. – Jeff L Apr 04 '22 at 04:50
  • @JeffL Posted an answer. The generation method ended up being pretty important for answering the question. In the future, it would be ideal to include this sort of information, even if you believe that it may not be immediately relevant. – BrokenBenchmark Apr 04 '22 at 05:05

2 Answers2

0

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)
BrokenBenchmark
  • 18,126
  • 7
  • 21
  • 33
  • Thanks, this pretty much solves it. But FYI there are some bugs in your answer. `if angles[0] < angles[i + 1]` should be `if angles[0] < angles[(i + 1) % len(angles)]` and you also must add `2 * pi` to `angle` if `angle < angles[0]`. i.e. your program breaks if `angle` is `-pi`. – Jeff L Apr 04 '22 at 05:25
  • Edited, thanks. Let me know if you see any other issues. – BrokenBenchmark Apr 04 '22 at 06:40
  • This also doesn't work if `angles[0]` and `angles[1]` are both negative. – Jeff L Apr 04 '22 at 06:49
  • I'm not able to reproduce the issue you're describing. – BrokenBenchmark Apr 04 '22 at 06:51
  • Let `angles = [-pi/3, -pi/6, pi/4, 5*pi/6, -2*pi/3]` Then choose `angle=-pi/4`. You'd want to add `2*pi` to `-pi/6` and `angle`, but this logic won't do that. – Jeff L Apr 04 '22 at 07:13
  • Why? Neither are less than `-pi/3`. – BrokenBenchmark Apr 04 '22 at 16:22
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/243593/discussion-between-jeff-l-and-brokenbenchmark). – Jeff L Apr 04 '22 at 18:43
0

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)
Mostafa Nazari
  • 616
  • 7
  • 21