Skip to content

Instantly share code, notes, and snippets.

@mayonesa
Last active May 12, 2017 23:32
Show Gist options
  • Save mayonesa/883837516b3f1595c6c12b5b5c7a5149 to your computer and use it in GitHub Desktop.
Save mayonesa/883837516b3f1595c6c12b5b5c7a5149 to your computer and use it in GitHub Desktop.
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
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