diff --git a/fire_drake.py b/fire_drake.py new file mode 100644 index 0000000..4f18d7b --- /dev/null +++ b/fire_drake.py @@ -0,0 +1,15 @@ +from matrix import Matrix +from vector import Vector + +current_drakes = Vector([10, 0, 0, 0, 0, 0]) + +leslie = Matrix([[0, .25, .6, .8, .15, 0], + [.7, 0, 0, 0, 0, 0], + [0, .95, 0, 0, 0, 0], + [0, 0, .9, 0, 0, 0], + [0, 0, 0, .9, 0, 0], + [0, 0, 0, 0, .5, 0]]) + + +drakes_future = leslie ** 20 * current_drakes +print(drakes_future) diff --git a/matrix.py b/matrix.py new file mode 100644 index 0000000..654df75 --- /dev/null +++ b/matrix.py @@ -0,0 +1,234 @@ +from vector import Vector, ShapeException + +import random +from numbers import Number + + +class Matrix: + + def __init__(self, vectors, shape=None): + if vectors: + self.vectors = tuple(self.check_vectors(vectors)) + self.shape = (len(vectors), self.vectors[0].shape[0]) + + else: + if shape: + self.shape = self.check_shape(shape) + return Matrix.zeroes(shape) + + @classmethod + def generate(cls, shape, gen_func): + """ + creates a matrix whose values + are the result of calling gen_func + """ + if cls.check_shape(cls, shape): + return cls([[gen_func() for _ in range(shape[0])] + for _ in range(shape[1])]) + + raise ShapeException + + @classmethod + def from_function(cls, shape, func): + if cls.check_shape(cls, shape): + return cls([[func(i, j) for j in range(shape[0])] for + i in range(shape[1])]) + + @classmethod + def zeroes(cls, shape): + """ + Generates a matrix of zeroes with the given shape + """ + cls.check_shape(cls, shape) + values = [] + + for y_val in range(shape[0]): + values.append([0 for _ in range(shape[1])]) + + return cls(values) + + @classmethod + def random(cls, shape, start=0, end=10, random=random.Random()): + """ + takes a random number generator + and fills a matrix of a given shape + with random numbers + """ + vectors = [] + try: + cls.check_shape(cls, shape) + except ShapeException: + raise + + for y_len in range(shape[0]): + vectors.append([random.randint(start, end) + for _ in range(shape[1])]) + + return cls(vectors) + + def check_vectors(self, vectors): + """ + checks the input vectors + to see if they are in fact + lists or vectors, then checks + their lengths. If they are + all the same length it returns + a list of vectors + """ + vect_type = [item for item in vectors if isinstance(item, Vector)] + list_type = [item for item in vectors if isinstance(item, list)] + none_type = [item for item in vectors + if item not in vect_type and item not in list_type] + + if none_type: + raise TypeError("supplied vectors are of incorrect type") + + try: + if list_type and not vect_type: + lengths = [len(item) for item in vectors] + + for item in lengths: + if item != lengths[0]: + raise ShapeException("unsuitable vectors used") + + return [Vector(item) for item in vectors] + + if vect_type and not list_type: + shapes = [item.shape for item in vectors] + + for item in shapes: + if item != shapes[0]: + raise ShapeException("unsuitable vectors used") + + return vectors + + raise TypeError("mismatched input types") + + except ShapeException: + raise + + except Exception: + + return ValueError("bad initial vectors") + + def check_shape(self, shape): + """ + tests whether shape is acceptable + e.g. a single integer or + two integers in a list/tuple + """ + size_check = (len(shape) == 1 or len(shape) == 2) + type_check = True + for entry in shape: + type_check = type_check and isinstance(entry, int) + + if not (size_check and type_check): + raise ShapeException + + else: + return True + + def __eq__(self, other): + try: + return self.shape == other.shape and self.vectors == other.vectors + + except Exception: + raise + + def __mul__(self, other): + if isinstance(other, Number): + return self.scalar_mult(other) + + if isinstance(other, Vector): + try: + return self.vector_mult(other) + except ShapeException: + raise + + if isinstance(other, Matrix): + try: + return self.matrix_mult(other) + except ShapeException: + raise + + @property + def transpose(self): + """ + the transpose of the matrix + """ + return Matrix([[row.values[i] for row in self.vectors] + for i in range(self.shape[1])]) + + def scalar_mult(self, other): + """ + multiplies the matrix by a constant + """ + return Matrix([vector * other for vector in self.vectors]) + + def vector_mult(self, other): + """ + multiplies a vector by self + and returns the resulting vector + """ + return Vector([vector.dot(other) for vector in self.vectors]) + + def matrix_mult(self, other): + """ + multiplies the matrix by a another matrix + and returns the resulting matrix + """ + if self.shape == other.shape: + return Matrix([[vect1.dot(vect2) for vect2 in + other.transpose.vectors] + for vect1 in self.vectors]) + + raise ShapeException + + def __pow__(self, other): + if not isinstance(other, int): + raise TypeError("power must be an integer") + + if other < 1: + raise TypeError("power must be positive") + + return self.remult(other) + + def remult(self, num): + """ + returns a matrix raised to a positive integer power + """ + if num > 1: + return self.remult(num - 1) * self + else: + return self + + def __add__(self, other): + try: + if self.shape == other.shape: + return Matrix([row1 + row2 for row1, row2 + in zip(self.vectors, other.vectors)]) + + except Exception: + raise + + def __sub__(self, other): + return self.__add__(other.__neg__()) + + def __neg__(self): + return self.__mul__(-1) + + def __rmul__(self, other): + if isinstance(other, Number): + return self.__mul__(other) + + def __iadd__(self, other): + return self.__add__(other) + + def __isub__(self, other): + return self.__sub__(other) + + def __str__(self): + return "\n".join([vector.__str__() for vector in self.vectors]) + + def __repr__(self): + return self.__str__() diff --git a/test_matrix.py b/test_matrix.py new file mode 100644 index 0000000..ac67832 --- /dev/null +++ b/test_matrix.py @@ -0,0 +1,121 @@ +from matrix import Matrix +from vector import Vector, ShapeException + +import nose +from nose.tools import raises + + +def test_zeroes(): + zeroes = Matrix.zeroes((3, 2)) + assert zeroes == Matrix([[0, 0]] * 3) + + +@raises(ShapeException) +def test_zeroes_bad_shape(): + Matrix.zeroes((11, 2, 3)) + + +@raises(ShapeException) +def test_matrix_bad_shape(): + Matrix([[0, 0, 0], [0, 0]]) + + +class Zeroes: + + def randint(self, start, end): + return 0 + + +class Ones: + + def randint(self, start, end): + return 1 + + +class Count: + + def __init__(self): + self.int = 0 + + def randint(self, start, end): + self.int += 1 + return self.int + + +def test_random(): + shape = (3, 5) + assert Matrix.random(shape, 0, 5, Zeroes()) == Matrix.zeroes(shape) + + assert Matrix.random(shape, 0, 5, Ones()) == Matrix([[1, 1, 1, 1, 1]] * 3) + + assert Matrix.random(shape, 0, 5, Count()) == \ + Matrix([[1, 2, 3, 4, 5], + [6, 7, 8, 9, 10], + [11, 12, 13, 14, 15]]) + + +def test_matrix_add_mult(): + identity = Matrix([[1, 0, 0], + [0, 1, 0], + [0, 0, 1]]) + + a_mat = Matrix([[1, 3, 2], + [2, 3, 1], + [3, 1, 2]]) + + b_mat = Matrix([[1, 1, 1], + [1, 1, 1], + [1, 1, 1]]) + + assert identity * a_mat == a_mat * identity + assert identity * a_mat == a_mat + + assert identity + a_mat == Matrix([[2, 3, 2], + [2, 4, 1], + [3, 1, 3]]) + + assert identity + a_mat == a_mat + identity + + assert a_mat - identity == Matrix([[0, 3, 2], + [2, 2, 1], + [3, 1, 1]]) + + assert a_mat * b_mat != b_mat * a_mat + + +@raises(ShapeException) +def test_bad_matrix_mult(): + a_mat = Matrix([[1, 2], [3, 4]]) + b_mat = Matrix([[1, 3, 5], [2, 5, 5]]) + a_mat * b_mat + + +def test_mat_vect_mult(): + a_mat = Matrix([[1, 2], [3, 4]]) + vect = Vector([3, 5]) + assert a_mat * vect == Vector([13, 29]) + + +@raises(ShapeException) +def test_bad_shape_vect_mult(): + a_mat = Matrix([[1, 2], [3, 4]]) + vect = Vector([3, 5, 6]) + a_mat * vect + + +@raises(ValueError) +def test_bad_order_mult(): + a_mat = Matrix([[1, 2], [3, 4]]) + vect = Vector([3, 5]) + vect * a_mat + + +def test_generate(): + def func(): + return 1 + + a_mat = Matrix.generate((2, 2), func) + assert a_mat == Matrix([[1, 1], [1, 1]]) + +if __name__ == '__main__': + nose.main() diff --git a/test_vector.py b/test_vector.py new file mode 100644 index 0000000..1f5223b --- /dev/null +++ b/test_vector.py @@ -0,0 +1,76 @@ +from vector import Vector, ShapeException + +from nose.tools import raises +import nose + + +def test_zeroes(): + zeroes = Vector.zeroes(10) + assert zeroes.values == tuple([0] * 10) + + +def test_check_shape(): + vect = Vector([1, 2, 3]) + assert vect.shape == (3,) + assert vect.check_shape(Vector([2, 4, 6])) + assert not vect.check_shape(Vector([1, 3])) + + +@raises(TypeError) +def test_vector_bad_shape(): + Vector([0, 0, [0, 0, 0], 0, 0]) + + +@raises(TypeError) +def test_zeroes_bad_shape(): + Vector.zeroes([[11, 2], 3]) + + +class Zeroes: + + def randint(self, start, end): + return 0 + + +class Ones: + + def randint(self, start, end): + return 1 + + +class Count: + + def __init__(self): + self.int = 0 + + def randint(self, start, end): + self.int += 1 + return self.int + + +def test_random(): + length = 5 + assert Vector.random(length, 0, 10, Zeroes()) == Vector.zeroes(length) + assert Vector.random(length, 0, 10, Ones()) == Vector([1] * length) + assert Vector.random(length, 0, 10, Count()) == Vector([1, 2, 3, 4, 5]) + + +def test_vector_add(): + assert Vector([1, 3, 3]) + Vector([1, 2, 3]) == Vector([2, 5, 6]) + assert Vector([1, 3, 3]) - Vector([1, 2, 3]) == Vector([0, 1, 0]) + + +@raises(ShapeException) +def test_bad_add(): + Vector([1, 2, 3]) + Vector([1, 2]) + + +def test_scalar_mult(): + assert Vector([1, 1, 1]) * 5 == Vector([5, 5, 5]) + + +def test_dot(): + Vector([1, 3, 5]).dot(Vector([3, 1, 5])) == 16 + +if __name__ == '__main__': + nose.main() diff --git a/vector.py b/vector.py new file mode 100644 index 0000000..69113ed --- /dev/null +++ b/vector.py @@ -0,0 +1,118 @@ +import random +import math +from numbers import Number + + +class ShapeException(Exception): + pass + + +class Vector: + + def __init__(self, arg): + + if not isinstance(arg, list): + raise ShapeException + + for item in arg: + if not (isinstance(item, Number)): + raise TypeError("List items not ints or floats") + + self.values = tuple(arg) + self.shape = (len(arg),) + + @classmethod + def zeroes(cls, length): + """ + makes a zero vector of the given length + """ + if not isinstance(length, int): + raise TypeError("input length is not an int") + + return cls([0] * length) + + @classmethod + def random(cls, length, start, end, random=random.Random()): + """ + makes a vector of the given length + with entries from random.randint(start, end) + """ + if not (isinstance(length, int) or + isinstance(start, int) or + isinstance(end, int)): + + raise ValueError + + return cls([random.randint(start, end) for _ in range(length)]) + + def check_shape(self, other): + """ + checks if the shapes match between + the vector and other + """ + return self.shape == other.shape + + def __add__(self, other): + if self.check_shape(other): + return Vector([entry1 + entry2 for entry1, entry2 + in zip(self.values, other.values)]) + + raise ShapeException + + def __sub__(self, other): + return self.__add__(other.__neg__()) + + def __neg__(self): + return self.__mul__(-1) + + def __iadd__(self, other): + return self.__add__(other) + + def __isub__(self, other): + return self.__sub__(other) + + def __mul__(self, other): + if isinstance(other, int) or isinstance(other, float): + return self.scalar_mult(other) + + else: + raise ValueError("Cannot multiply {} and {}".format(type(self), + type(other))) + + def __rmul__(self, other): + if isinstance(other, Number): + return self.__mul__(other) + + def scalar_mult(self, other): + """ + multiplies the vector by a scalar + and returns the result + """ + return Vector([val * other for val in self.values]) + + def __eq__(self, other): + return (type(self) == type(other) and + self.shape == other. shape and + self.values == other.values) + + def dot(self, other): + if self.check_shape(other): + return sum([coeff1 * coeff2 for (coeff1, coeff2) + in list(zip(self.values, other.values))]) + + raise ShapeException + + @property + def magnitude(self): + """ + calculates the magnitude of the vector + in the E + """ + return math.sqrt(self.dot(self)) + + def __str__(self): + return "[" + ", ".join([str(round(val, 3)) + for val in self.values]) + "]" + + def __repr__(self): + return self.__str__()