98 lines
4.0 KiB
Python
98 lines
4.0 KiB
Python
import math
|
||
import decimal
|
||
decimal.getcontext().prec = 30
|
||
|
||
|
||
class Vector(object):
|
||
CANNOT_NORMALIZE_ZERO_VECTOR_MSG = 'Cannot normalize the zero vector'
|
||
|
||
def __init__(self, coordinates):
|
||
try:
|
||
if not coordinates:
|
||
raise ValueError
|
||
self.coordinates = tuple([decimal.Decimal(x) for x in coordinates])
|
||
self.dimension = len(coordinates)
|
||
|
||
except ValueError:
|
||
raise ValueError('The coordinates must be nonempty')
|
||
|
||
except TypeError:
|
||
raise TypeError('The coordinates must be an iterable')
|
||
|
||
def __str__(self):
|
||
return 'Vector: {}'.format(self.coordinates)
|
||
|
||
def __eq__(self, v):
|
||
return self.coordinates == v.coordinates
|
||
|
||
# LINEAR ALGEBRA METHODS
|
||
def plus(self, v):
|
||
new_coordinates = [x+y for x,
|
||
y in zip(self.coordinates, v.coordinates)]
|
||
return Vector(new_coordinates)
|
||
|
||
def minus(self, v):
|
||
new_coordinates = [x-y for x,y in zip(self.coordinates, v.coordinates)]
|
||
return Vector(new_coordinates)
|
||
|
||
def times_scalar(self,c):
|
||
new_coordinates = [c*x for x in self.coordinates]
|
||
return Vector(new_coordinates)
|
||
|
||
def magnitude(self):
|
||
coordinates_squared = [x**2 for x in self.coordinates]
|
||
return decimal.Decimal(math.sqrt(sum(coordinates_squared)))
|
||
|
||
def normalized(self):
|
||
try:
|
||
magnitude = self.magnitude()
|
||
return self.times_scalar(decimal.Decimal('1.0')/magnitude)
|
||
except ZeroDivisionError:
|
||
raise Exception(self.CANNOT_NORMALIZE_ZERO_VECTOR_MSG)
|
||
|
||
def dot(self,v):
|
||
return sum([x*y for x,y in zip(self.coordinates, v.coordinates)])
|
||
|
||
def angle_inner_with(self, v, in_degrees = False):
|
||
try:
|
||
u1 = self.normalized()
|
||
u2 = v.normalized()
|
||
#Capture within range of dot product to be within 1 & -1
|
||
u1u2dot = replace_if_within_tolerance(u1.dot(u2),1)
|
||
u1u2dot = replace_if_within_tolerance(u1.dot(u2),-1)
|
||
angle_in_radians = math.acos(u1u2dot)
|
||
|
||
if in_degrees:
|
||
return math.degrees(angle_in_radians)
|
||
else:
|
||
return angle_in_radians
|
||
except Exception as e:
|
||
if str(e) == self.CANNOT_NORMALIZE_ZERO_VECTOR_MSG:
|
||
raise Exception('Cannot compute an angle with the zero vector')
|
||
else:
|
||
raise e def is_zero(self, tolerance = 1e-10):
|
||
#returns true if magnitude is less than tolerance
|
||
return self.magnitude() < tolerance
|
||
|
||
def is_parallel_to(self, v):
|
||
return ( self.is_zero() or
|
||
v.is_zero() or
|
||
self.angle_inner_with(v) == 0 or
|
||
self.angle_inner_with(v) == math.pi )
|
||
|
||
def is_orthogonal_to(self, v, tolerance = 1e-10):
|
||
return abs(self.dot(v)) < tolerance
|
||
|
||
def replace_if_within_tolerance(val, compared_against, tolerance = 1e-10):
|
||
if abs(val - compared_against) < tolerance:
|
||
return compared_against
|
||
else:
|
||
return val
|
||
|
||
#Values from Parallel Orthogonal Quiz, math domain error
|
||
|
||
v = Vector([’-7.579’, ‘-7.88’])
|
||
w = Vector([‘22.737’, ‘23.64’])
|
||
print(v.is_parallel_to(w)) print(v.is_orthogonal_to(w))
|
||
#v = Vector([’-2.029’, ‘9.97’, ‘4.172’]) w = Vector([’-9.231’, ‘-6.639’, ‘-7.245’]) print(v.is_parallel_to(w)) print(v.is_orthogonal_to(w)) v = Vector([’-2.328’, ‘-7.284’, ‘-1.214’]) w = Vector([’-1.821’, ‘1.072’, ‘-2.94’]) print(v.is_parallel_to(w)) print(v.is_orthogonal_to(w)) v = Vector([‘2.118’, ‘4.827’]) w = Vector([‘0’, ‘0’]) print(v.is_parallel_to(w)) print(v.is_orthogonal_to(w))
|