Last active
May 12, 2017 23:32
-
-
Save mayonesa/883837516b3f1595c6c12b5b5c7a5149 to your computer and use it in GitHub Desktop.
This file contains 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
from decimal import Decimal, getcontext | |
from vector import Vector | |
getcontext().prec = 30 | |
class Line(object): | |
__NO_NONZERO_ELTS_FOUND_MSG = 'No nonzero elements found' | |
def __init__(self, normal_vector=None, constant_term=None): | |
self.__dimension = 2 | |
if not normal_vector: | |
all_zeros = ['0']*self.__dimension | |
normal_vector = Vector(all_zeros) | |
self.__normal_vector = normal_vector | |
if not constant_term: | |
constant_term = Decimal('0') | |
self.__constant_term = Decimal(constant_term) | |
self.__set_basepoint() | |
def __set_basepoint(self): | |
try: | |
n = self.__normal_vector | |
c = self.__constant_term | |
basepoint_coords = ['0']*self.__dimension | |
initial_index = Line.first_nonzero_index(n) | |
initial_coefficient = n[initial_index] | |
basepoint_coords[initial_index] = c / Decimal(initial_coefficient) | |
self.__basepoint = Vector(basepoint_coords) | |
except Exception as e: | |
if str(e) == Line.__NO_NONZERO_ELTS_FOUND_MSG: | |
self.__basepoint = None | |
else: | |
raise e | |
def is_parallel_to(self, other): | |
return self.__normal_vector.is_parallel_to(other.__normal_vector) | |
def intersection(self, other): | |
if self.is_parallel_to(other): | |
return None | |
ls = self._LineSystem(other, self if self.__normal_vector[0] == 0 else self, other) | |
ad_minus_bc = ls.a * ls.d - ls.b * ls.c | |
return ((ls.d * ls.k1 - ls.b * ls.k2) / ad_minus_bc, (ls.a * ls.k2 - ls.c * ls.k1) / ad_minus_bc) | |
def __eq__(self, other): | |
return (self.is_parallel_to(other) and | |
(self.__basepoint - other.__basepoint).is_orthogonal_to(self.__normal_vector)) | |
def __str__(self): | |
num_decimal_places = 3 | |
def write_coefficient(coefficient, is_initial_term=False): | |
coefficient = round(coefficient, num_decimal_places) | |
if coefficient % 1 == 0: | |
coefficient = int(coefficient) | |
output = '' | |
if coefficient < 0: | |
output += '-' | |
if coefficient > 0 and not is_initial_term: | |
output += '+' | |
if not is_initial_term: | |
output += ' ' | |
if abs(coefficient) != 1: | |
output += '{}'.format(abs(coefficient)) | |
return output | |
n = self.__normal_vector | |
try: | |
initial_index = Line.first_nonzero_index(n) | |
terms = [write_coefficient(n[i], is_initial_term=(i==initial_index)) + 'x_{}'.format(i+1) | |
for i in range(self.__dimension) if round(n[i], num_decimal_places) != 0] | |
output = ' '.join(terms) | |
except Exception as e: | |
if str(e) == self.__NO_NONZERO_ELTS_FOUND_MSG: | |
output = '0' | |
else: | |
raise e | |
constant = round(self.__constant_term, num_decimal_places) | |
if constant % 1 == 0: | |
constant = int(constant) | |
output += ' = {}'.format(constant) | |
return output | |
@staticmethod | |
def first_nonzero_index(iterable): | |
for k, item in enumerate(iterable): | |
if not MyDecimal(item).is_near_zero(): | |
return k | |
raise Exception(Line.NO_NONZERO_ELTS_FOUND_MSG) | |
class _LineSystem: | |
def __init__(self, *l1l2): | |
l1 = l1l2[0] | |
l2 = l1l2[1] | |
self.a = l1._Line__normal_vector[0] | |
self.b = l1._Line__normal_vector[1] | |
self.k1 = float(l1._Line__constant_term) | |
self.c = l2._Line__normal_vector[0] | |
self.d = l2._Line__normal_vector[1] | |
self.k2 = float(l2._Line__constant_term) | |
class MyDecimal(Decimal): | |
def is_near_zero(self, eps=1e-10): | |
return abs(self) < eps |
This file contains 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
from operator import add, mul | |
from math import sqrt, acos, pi | |
class Vector(object): | |
def __init__(self, coordinates): | |
try: | |
if not coordinates: | |
raise ValueError | |
self.__coordinates = tuple(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 __add__(self, other): | |
return Vector(map(add, self.__coordinates, other.__coordinates)) | |
def __sub__(self, other): | |
return Vector(map(lambda x, y: float(x) - float(y), self.__coordinates, other.__coordinates)) | |
def __mul__(self, other): | |
return Vector(self.__coord_mult(other) | |
if isinstance(other, self.__class__) | |
else map(lambda x: other * x, self.__coordinates)) | |
def __getitem__(self, i): | |
return self.__coordinates[i] | |
def __len__(self): | |
return len(self.__coordinates) | |
def magnitude(self): | |
return sqrt(reduce(lambda acc, x: acc + x**2, self.__coordinates, 0)) | |
def normalized(self): | |
try: | |
return self * (1. / self.magnitude()) | |
except ZeroDivisionError: | |
raise Exception('Cannot normalize the zero vector') | |
def dot(self, other): | |
return sum(self.__coord_mult(other)) | |
def theta(self, other, in_degrees = False): | |
rads = acos(round(self.dot(other) / self.magnitude() / other.magnitude(), 3)) | |
return rads * 180. / pi if in_degrees else rads | |
def is_parallel_to(self, other): | |
if self.is_zero_vector() or other.is_zero_vector(): | |
return True | |
theta = self.theta(other) | |
return Vector.is_tolerably_equal(theta, 0) or Vector.is_tolerably_equal(theta, pi) | |
def is_orthogonal_to(self, other, tolerance = 1e-10): | |
return Vector.is_tolerably_equal(self.dot(other), 0) | |
def project_unto(self, basis): | |
basis_unit = basis.normalized() | |
return basis_unit * self.dot(basis_unit) | |
def component_orthogonal_to(self, basis): | |
return self.orthogonal_component(self.project_unto(basis)) | |
def orthogonal_component(self, parallel_component): | |
return self - parallel_component | |
def cross(self, other): | |
return Vector([self[1] * other[2] - other[1] * self[2], | |
self[0] * other[2] - other[0] * self[2], | |
self[0] * other[1] - other[0] * self[1]]) | |
def parellelogram_area(self, other): | |
vs = self.__third_dim(other) | |
return vs[0].cross(vs[1]).magnitude() | |
def triangle_area(self, other): | |
return .5 * self.parellelogram_area(other) | |
def is_zero_vector(self): | |
def loop(i): | |
return (loop(i - 1) if self[i] == 0 else False) if i >= 0 else True | |
return loop(len(self) - 1) | |
@staticmethod | |
def is_tolerably_equal(x, y, tolerance = 1e-4): | |
return abs(x - y) <= tolerance | |
@staticmethod | |
def __plus_0z(v): | |
return Vector([v[0], v[1], 0]) | |
def __third_dim(self, other): | |
return (Vector.__plus_0z(self), Vector.__plus_0z(other)) if len(self) == 2 else (self, other) | |
def __coord_mult(self, other): | |
return map(mul, self.__coordinates, other.__coordinates) | |
def __str__(self): | |
return 'Vector: {}'.format(self.__coordinates) | |
def __eq__(self, v): | |
return self.coordinates == v.__coordinates |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment