GlenS
GlenS

Reputation: 227

ValueError: math domain error from math.acos and NaN from numpy.arccos

I've seen similar questions asked but I haven't found an answer to help me yet. I'm trying to find the angle between two vectors using the dot product method.

import math as m
import numpy as np

def mag(x):
    return np.sqrt(np.sum(i**2 for i in x))

u = np.array([1,1,1])
v = np.array([-1,-1,-1])

theta = m.degrees(np.arccos(np.dot(u,v) / (mag(u) * mag(v))))

It works for most cases but when I set u and v to vectors that are 180deg apart (as above) apart I get ValueError: math domain error. I switched from m.acos to np.arccos (as above) which returns NaN but it's essentially the same problem. I know this caused by floating point rounding errors producing a value that is slightly below -1 which is outside the domain of acos/arrcos but I can't figure out what to do about it.

print('theta = ', theta)
print('magnitude product = ', mag(u) * mag(v))
print('dot product = ', np.dot(u,v))
print('dot prod / mag prod = ', np.dot(u,v) / (mag(u) * mag(v)))
print('dot prod / mag prod < -1.0 = ', (np.dot(u,v) / (mag(u) * mag(v))) < -1.0)

theta =  nan
magnitude product =  3.0
dot product =  -3
dot prod / mag prod =  -1.0
dot prod / mag prod < -1.0 =  True

I've tried using the decimal module but so far only managed to make things worse. I can't imagine this is an unusual problem so I'm guessing there's a good, clean solution somewhere but I just can't find it.

Upvotes: 6

Views: 5450

Answers (2)

Eric
Eric

Reputation: 97601

Moving the square root at least fixes the inputs you provide, since then the result remains an integer at all intermediate steps

import numpy as np

def mag2(x):
    return np.dot(x, x)  # or np.sum(x ** 2)

u = np.array([1,1,1])
v = np.array([-1,-1,-1])

theta = np.degrees(np.arccos(np.dot(u,v) / np.sqrt(mag2(u) * mag2(v))))

Upvotes: 1

aiven
aiven

Reputation: 4323

The problem is with floating point. Result of np.dot(u,v) / (mag(u) * mag(v)) can be something like -1.000000000000002 and this is not valid number for acos (coz cos must be in range [-1, 1])

I suggest you to use np.clip:

def mag(x):
    return np.sqrt(np.sum(i ** 2 for i in x))

u = np.array([1, 1, 1])
v = np.array([-1, -1, -1])

cos = np.dot(u, v) / (mag(u) * mag(v))
cos = np.clip(cos, -1, 1)
rad = np.arccos(cos)  # or m.acos(cos)
print(rad)
theta = m.degrees(rad)
print(theta)

Upvotes: 12

Related Questions