Last active
January 29, 2019 21:40
-
-
Save badgateway666/ba9aa21874284274193d3367ad5d4cb2 to your computer and use it in GitHub Desktop.
My vector implementation taking advantage of python's built-in Magic-Methods
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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