Skip to content

Instantly share code, notes, and snippets.

@badgateway666
Last active January 29, 2019 21:40
Show Gist options
  • Save badgateway666/ba9aa21874284274193d3367ad5d4cb2 to your computer and use it in GitHub Desktop.
Save badgateway666/ba9aa21874284274193d3367ad5d4cb2 to your computer and use it in GitHub Desktop.
My vector implementation taking advantage of python's built-in Magic-Methods
import math
import numbers
import random
class Vector(object):
""" My pythonic vector implementation """
def __init__(self, *args):
if len(args) == 1 and isinstance(args[0], list):
args = args[0]
for err_val in filter(lambda val: not isinstance(val, numbers.Real), args):
raise ValueError('Illegal Value "{}". Vector components need to be real numbers.'.format(err_val))
self.dim = len(args)
self.components = [val for val in args]
@classmethod
def create_random(cls, dim=3, range_start=-10, range_end=11):
return cls(*[random.randint(range_start, range_end) for _ in range(dim)])
# TODO
@classmethod
def create_unit_vector(cls, dim=3, nth_unit_vector=0):
return cls()
@classmethod
def create_null_vector(cls, dim=3):
return cls(*[0 for _ in range(dim)])
def __repr__(self):
return "<{} dim={} magnitude={:.5f}.. {}>".format(self.__class__.__name__, len(self), abs(self),
self.components)
def __str__(self):
return "<{} dim={} {}>".format(self.__class__.__name__, len(self), self.components)
def __len__(self):
""" Returns the vectors dimension. """
return self.dim
def __abs__(self):
""" Returns the magnitude of a vector. """
return self.magnitude()
def __getitem__(self, item):
""" Returns the (n+1)-th component of vector. """
if isinstance(item, int):
if item >= len(self):
raise IndexError("Tried to access {}-th component of {}-dimensional vector".format(item + 1, len(self)))
return self.components[item]
elif isinstance(item, slice):
return self.components[item.start:item.stop:item.step]
elif isinstance(item, list):
return [self.components[i] for i in item]
else:
raise TypeError
def __lt__(self, other):
""" Compares Vector with < operator to other.
If other is a real number, magnitude of vector is compared to that number.
If other is a vector, their magnitudes are compared.
"""
if isinstance(other, Vector):
if not len(self) == len(other):
raise ValueError("Only vectors of same dimensions can be compared.")
return self.magnitude_sq() < other.magnitude_sq()
elif isinstance(other, numbers.Real):
return abs(self) < other
else:
raise TypeError("Vectors can only be compared to other vectors or real numbers (compares magnitude). ")
def __le__(self, other):
""" Compares Vector with <= operator to other.
If other is a real number, magnitude of vector is compared to that number.
If other is a vector, their magnitudes and directions are compared.
"""
if isinstance(other, Vector):
if not len(self) == len(other):
raise ValueError("Only vectors of same dimensions can be compared.")
return not any([val - other_val for val, other_val in zip(self, other)]) or \
self.magnitude_sq() < other.magnitude_sq()
# self.magnitude_sq() <= other.magnitude_sq() # TODO
elif isinstance(other, numbers.Real):
return abs(self) <= other
else:
raise TypeError("Vectors can only be compared to other vectors or real numbers (compares magnitude). ")
def __eq__(self, other):
""" Compares Vector with == operator to other.
If other is a real number, magnitude of vector is compared to that number.
If other is a vector, their magnitudes and directions must be equal.
TODO: What about vectors with different components but same magnitude?
"""
if isinstance(other, numbers.Real):
return abs(self) == other
if isinstance(other, Vector):
if not len(self) == len(other):
raise ValueError("Only vectors of same dimensions can be compared.")
return not any([val - other_val for val, other_val in zip(self, other)])
else:
raise TypeError("Vectors can only be compared for equality to other vectors. ")
def __ge__(self, other):
""" Compares Vector with >= operator to other.
If other is a real number, magnitude of vector is compared to that number.
If other is a vector, their magnitudes and components are compared.
"""
if isinstance(other, Vector):
if not len(self) == len(other):
raise ValueError("Only vectors of same dimensions can be compared.")
return not any([val - other_val for val, other_val in zip(self, other)]) or \
self.magnitude_sq() > other.magnitude_sq()
# self.magnitude_sq() >= other.magnitude_sq() # TODO
elif isinstance(other, numbers.Real):
return abs(self) >= other
else:
raise TypeError("Vectors can only be compared to other vectors or real numbers (compares magnitude). ")
def __gt__(self, other):
""" Compares Vector with > operator to other.
If other is a real number, magnitude of vector is compared to that number.
If other is a vector, their magnitudes are compared.
"""
if isinstance(other, Vector):
if not len(self) == len(other):
raise ValueError("Only vectors of same dimensions can be compared.")
return self.magnitude_sq() > other.magnitude_sq()
elif isinstance(other, numbers.Real):
return abs(self) > other
else:
raise TypeError("Vectors can only be compared to other vectors or real numbers (compares magnitude). ")
def __add__(self, other):
""" Adds each component of other vector to each component of vector if they are of same dimension. """
if not isinstance(other, Vector):
raise TypeError("Can only add a vector to another vector.")
if not len(self) == len(other):
raise ValueError("Only vectors of same dimensions can be added.")
return Vector(*[val + other_val for val, other_val in zip(self.components, other.components)])
def __sub__(self, other):
""" Substracts each component of other vector from each component of vector if they are of same dimension. """
if not isinstance(other, Vector):
raise TypeError("Can only substract a vector from another vector.")
if not len(self) == len(other):
raise ValueError("Only vectors of same dimensions can be substracted.")
return Vector(*[val - other_val for val, other_val in zip(self.components, other.components)])
def __mul__(self, other):
""" If other is a real number, do a scalar-multiplication.
If other is a vector of same dimension, calculate dot-product. """
if isinstance(other, numbers.Real):
return Vector(*[val * other for val in self.components])
elif isinstance(other, Vector):
if not len(self) == len(other):
raise ValueError("Dot-product is only defined for vectors of same dimensions.")
return self.dot_product(other)
else:
raise TypeError
def __truediv__(self, other):
""" Vectors can only be divided by a scalar divisor. """
if isinstance(other, numbers.Real):
if other == 0:
raise ValueError("Cannot divide by zero.")
return self * (1 / other)
else:
raise TypeError("Vectors can only be divided by a scalar divisor.")
def __pow__(self, power, modulo=None):
pass
def is_null_vector(self):
""" Returns true if all components of a vector are equal to zero. """
return not any(self.components)
def magnitude(self):
""" Magnitude of a vector is defined as the dot-product with itself,
or as the square root of the sum of the products of each component with itself. """
return math.sqrt(sum([val ** 2 for val in self.components]))
def magnitude_sq(self):
""" Squared magnitude of vector can be calculated without expensive math.sqrt function call. """
return sum([val ** 2 for val in self.components])
def scale(self, factor):
""" Scalar multiplication stretches/shrinks each component of a vector by the same factor. """
self.components = [val * factor for val in self.components]
def dot_product(self, other_vector):
""" Dot-product is defined for vectors of same dimensions and returns a scalar value. """
if not isinstance(other_vector, Vector):
raise TypeError
if not len(self) == len(other_vector):
raise ValueError("Dot-product is only defined for vectors of same dimensions.")
return sum([val * other_val for val, other_val in zip(self.components, other_vector.components)])
def cross_product(self, other_vector):
""" Cross-product is only defined for 3-dimensional vectors,
and returns a new vector orthogonal to each input vector """
if not isinstance(other_vector, Vector):
raise TypeError
if not len(self) == 3 or not len(other_vector) == 3:
raise ValueError("Cross-Product is only defined for 3-dimensional vectors.")
return Vector(self[1] * other_vector[2] - self[2] * other_vector[1],
self[2] * other_vector[0] - self[0] * other_vector[2],
self[0] * other_vector[1] - self[1] * other_vector[0])
def angle_to(self, other_vector, in_degrees=False):
""" Calculates angle between vectors of same dimensions. """
if not isinstance(other_vector, Vector):
raise TypeError
if not len(self) == len(other_vector):
raise ValueError("Angles between vectors can only be calculated if they are of same dimensions.")
result = math.acos(abs(self * other_vector) / (abs(self) * abs(other_vector)))
if in_degrees:
return math.degrees(result)
else:
return result
def is_orthogonal_to(self, other_vector):
""" Vectors are orthogonal to each other if their dot-product is zero. """
if not isinstance(other_vector, Vector):
raise TypeError
if not len(self) == len(other_vector):
raise ValueError("Vectors need to be of same dimension for calculating orthogonality.")
return self * other_vector == 0
def is_colinear_to(self, other):
""" Vectors are colinear to each other if the angle between them is either 0 or Pi """
if not isinstance(other, Vector):
raise TypeError
if not len(self) == len(other):
raise ValueError("Vectors need to be of same dimension for calculating orthogonality.")
angle = self.angle_to(other)
return angle % math.pi == 0
def distance_to(self, other_vector):
""" Calculates the distance to another vector. """
if not isinstance(other_vector, Vector):
raise TypeError
if not len(self) == len(other_vector):
raise ValueError("Vectors need to be of same dimension for calculating distance.")
return abs(other_vector - self)
def squared_distance_to(self, other_vector):
"""
Calculates the squared distance to another vector.
This removes the need for expensive math.sqrt calculations.
"""
if not isinstance(other_vector, Vector):
raise TypeError
if not len(self) == len(other_vector):
raise ValueError("Vectors need to be of same dimension for calculating distance.")
return (other_vector - self).magnitude_sq()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment