diff --git a/src/matrix.py b/src/matrix.py index df98c10..611921e 100644 --- a/src/matrix.py +++ b/src/matrix.py @@ -87,6 +87,14 @@ class Matrix: """ return Matrix(self.__data__.transpose()) + def T(self): + """ + Same as ``matrix.transpose()`` + + :return: see ``matrix.transpose()`` + """ + return self.transpose() + def __eq__(self, other): """ Return ``self==value`` diff --git a/src/vector.py b/src/vector.py index b6caedc..c9403df 100644 --- a/src/vector.py +++ b/src/vector.py @@ -1,13 +1,17 @@ +import numpy + from matrix import Matrix class Vector(Matrix): - def __init__(self, data: list | int): + def __init__(self, data): """ - :type data: list | int + :type data: numpy.ndarray | list | int """ - if isinstance(data, list): + if isinstance(data, numpy.ndarray): + super().__init__(data) + elif isinstance(data, list): super().__init__(data, (len(data), 1)) elif isinstance(data, int): self.__init__([0] * data) @@ -17,11 +21,16 @@ class Vector(Matrix): def get_dimension(self): return super().shape()[0] + def transpose(self): + return Vector(self.__data__.reshape(self.__shape__[1], self.__shape__[0])) + def __mul__(self, other): if isinstance(other, Vector): - return (super().transpose().__mul__(other))[0][0] + if self.shape() == other.shape(): + return Vector(self.__data__ * other.__data__) + return super().__mul__(other)[0][0] elif isinstance(other, int) or isinstance(other, float): - return super().__mul__(other) + return Vector(super().__mul__(other).__data__) else: raise ValueError("A vector can only be multiplied with an vector (dot product) or a scalar") @@ -29,7 +38,19 @@ class Vector(Matrix): return self * other def norm(self, **kwargs): + """ + Computes the 2-norm of the vector which is the Frobenius-Norm of a nx1 matrix. + + :param kwargs: ignored + :return: the 2-norm of the vector + """ return super().norm() def normalize(self): + """ + A normalized vector has the length (norm) 1. + To achieve that the vector is divided by the norm of itself. + + :return: the normalized vector + """ return self / self.norm() diff --git a/test/test_matrix.py b/test/test_matrix.py index 758fce1..4d7c565 100644 --- a/test/test_matrix.py +++ b/test/test_matrix.py @@ -98,7 +98,7 @@ class TestMatrix(TestCase): self.assertNotEqual(m1, m2) - def test_should_transpose_matrix(self): + def test_should_transpose_square_matrix(self): data = [1, 2, 3, 4, 5, 6, 7, 8, 9] actual = Matrix(data, (3, 3)) @@ -106,6 +106,14 @@ class TestMatrix(TestCase): expected = [[1, 4, 7], [2, 5, 8], [3, 6, 9]] self.assertEqual(expected, actual) + def test_should_transpose_nonsquare_matrix(self): + data = [1, 2, 3, 4, 5, 6] + actual = Matrix(data, (2, 3)) + + actual = actual.transpose() + expected = [[1, 4], [2, 5], [3, 6]] + self.assertEqual(expected, actual) + def test_should_negate_matrix(self): m = Matrix([1, 2, 3, 4], (2, 2)) diff --git a/test/test_serial.py b/test/test_serial.py new file mode 100644 index 0000000..f657095 --- /dev/null +++ b/test/test_serial.py @@ -0,0 +1,216 @@ +import numpy as np + +from matrix import Matrix +from vector import Vector + +np.random.seed(0) +############### +# Testing the vector class +print("\n\nTesting the vector class -----------------------------------\n\n") +############### + +### 1a Initialization +print("Start 1a initialization") +# from list +x1 = [1, 2, 3, 4, 5] +vec_x1 = Vector(x1) +# from numpy array +x2 = np.random.uniform(0, 1, 10000) +vec_x2 = Vector(x2) +print("End 1a\n") + +### 1b __str__ function, string representation +print("Start 1b string representation") +print(f"vec_x1 values: {str(vec_x1)}") +print(f"vec_x2 values: {str(vec_x2)}") +print("End 1b\n") + +### 1c shape and transpose +print("Start 1c shape and transpose") +# shape property of the vector +print(f"vec_x1 has shape {vec_x1.shape()} | must be (5,1)") # Hint: numpy.reshape +# transposition (property) of the vector, only "cosmetic" change +print(f"vec_x2.T() has shape {vec_x2.T().shape()} | must be (1,10000)") +print(f"vec_x2.T().T() has shape {vec_x2.T().T().shape()} | must be (10000,1)") +print("End 1c\n") + +### 1d addition and substraction +print("Start 1d addition and substraction") +# intitialization +a = Vector([1, 3, 5, 7, 9]) +b = Vector([-2, 5, 1, 0, -3]) +# computation +x = a + b +print(f"a + b = {str(x)} | must be {np.array([1, 3, 5, 7, 9]) + np.array([-2, 5, 1, 0, -3])}") +y = a - b +print(f"a - b = {str(y)} | must be {np.array([1, 3, 5, 7, 9]) - np.array([-2, 5, 1, 0, -3])}") +try: + x_false = a + b.T() +except ValueError as e: + print("The correct result is this error: " + str(e)) +else: + print("It is required to raise a system error, e. g., ValueError, since dimensions mismatch!") +try: + x_false = a - b.T() +except ValueError as e: + print("The correct result is this error: " + str(e)) +else: + print("It is required to raise a system error, e. g., ValueError, since dimensions mismatch!") +print("End 1d\n") + +### 1e multiplication +print("Start 1e multiplication") +# intitialization +a = Vector([1, 3, 5, 7, 9]) +b = Vector([-2, 5, 1, 0, -3]) +# computation with vectors +x = a.T() * b # scalar +print(f"a.T() * b = {str(x)} | must be {np.sum(np.array([1, 3, 5, 7, 9]) * np.array([-2, 5, 1, 0, -3]))}") +y = a * b # vector +print(f"a * b = {str(y)} | must be {np.array([1, 3, 5, 7, 9]) * np.array([-2, 5, 1, 0, -3])}") +z = b * a.T() # matrix +print(f"b * a.T() = \n{str(z)} | must be \n{np.outer(np.array([-2, 5, 1, 0, -3]), np.array([1, 3, 5, 7, 9]))}") +# computation with scalars +x = a * 5 +print(f"a * 5 = {str(x)} | must be {np.array([1, 3, 5, 7, 9]) * 5}") +y = 0.1 * b.T() +print(f"0.1 * b.T()= {str(y)} | must be {0.1 * np.array([-2, 5, 1, 0, -3])}") +print("End 1e\n") + +### 1f divison +print("Start 1f divison") +# intitialization +a = Vector([1, 3, 5, 7, 9]) +b = Vector([-2, 5, 1, 7, -3]) +# computation with vectors +x = a / b +print(f"a / b = str{x} | must be {np.array([1, 3, 5, 7, 9]) / np.array([-2, 5, 1, 7, -3])}") +y = a / 5 +print(f"a / 5 = str{y} | must be {np.array([1, 3, 5, 7, 9]) / 5} ") +print("End 1f\n") + +### 1g norm +print("Start 1g norm") +# intitialization +a = Vector([1, 3, 5, 7, 9]) +a_norm, a_normalized = a.normalize() +print(f"a_norm = {a_norm} | must be {np.linalg.norm([1, 3, 5, 7, 9])}") +print(f"a_normalize = {str(a_normalized)} | must be {np.array([1, 3, 5, 7, 9]) / np.linalg.norm([1, 3, 5, 7, 9])}") +print("End 1g\n") + +### 1h negation +print("Start 1h negation") +# intitialization +a = Vector([1, 3, 5, 7, 9]) +x = -a +print(f"-a = {str(x)} | must be {-np.array([1, 3, 5, 7, 9])}") +print("End 1h\n") + +### 1i manipulation +print("Start 1i manipulation") +# intitialization +a = Vector([1, 3, 5, 7, 9]) +print( + f"a[{str([1, 2, 4])}] = {str(a[1, 2, 4].reshape(3, ))} | must be {np.array([1, 3, 5, 7, 9]).reshape(5, 1)[np.array([1, 2, 4])].reshape(3, )}") +a[1, 2, 4] = [-1, -1, -1] +print(f"a = {str(a)} | must be {np.array([1, -1, -1, 5, 7, -1])}") +print("End 1i\n") + +############### +# Testing the matrix class +print("\n\nTesting the matrix class -----------------------------------\n\n") +############### + +### 1a Initialization +print("Start 2a initialization") +a_list = np.array([[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 2 * np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])]) +A = Matrix(a_list) +B = Matrix(structure="tridiagonal", given_size=11) +c_list = [[(i + 1) / (index + 1) for index in range(10)] for i in range(10)] +C = Matrix(c_list, given_shape=(10, 10)) +print("End 2a\n") + +### 1b __str__ function, string representation +print("Start 2b string representation") +# print(B.__str__(full = True)) +print(str(A)) +print(str(B)) +print(str(C)) +print("End 2b\n") + +### 1c shape and transpose +print("Start 2c shape and transpose") +# Initialization +A = Matrix(np.array([i for i in range(12)]).reshape(-1, 1), given_shape=(4, 3)) +print(f"A has shape {A.shape()} | must be (4,3)") +print(f"A.T() has shape {A.T().shape()} | must be (3,4)") +print(f"A.T().T() has shape {A.T().T().shape()} | must be (4,3)") +print("End 2c\n") + +### 1d addition and substraction +print("Start 2d addition and substraction") +# Initialization +A = Matrix(structure="diagonal", given_values=[3], offset=0, given_size=10) +print(str(A)) +A21 = Matrix(structure="diagonal", given_values=[-1], offset=-1, given_size=10) +print(str(A21)) +A12 = Matrix(structure="diagonal", given_values=[-1], offset=+1, given_size=10) +print(str(A12)) +B = Matrix(structure="diagonal", given_values=[1], offset=0, given_size=10) +print(str(B)) +# computation +C = A + A21 + A12 - B +print(str(C) + f"must be\n{Matrix(structure='tridiagonal', given_values=[-1, 2, -1], given_size=10)}") +print(str(5 + A - 3)) +print("End 2d\n") + +### 1e multiplication +print("Start 2e multiplication") +# initialization +a_mat = [[(i0 + 1) / (i1 + 1) for i1 in range(3)] for i0 in range(10)] +b_mat = [[(i0 + 1) / (i1 + 1) for i1 in range(10)] for i0 in range(3)] +A = Matrix(a_mat) +B = Matrix(b_mat) +# computation +# print(f"A * B =\n{str(A*B)}must be\n{str(np.round(np.array(a_mat) @ np.array(b_mat),decimals=3))}") +print(f"Norm of (A*B - np.(A*B)) is {(A * B - Matrix(np.array(a_mat) @ np.array(b_mat))).norm()} | must be < 1e-8") +# print(f"A.T() * A =\n{str(A.T()*A)}must be\n{str(np.round(np.array(a_mat).T@np.array(a_mat),decimals=3))}") +print( + f"Norm of (A.T()*A - np(A.T()*A)) is {(A.T() * A - Matrix(np.array(a_mat).T @ np.array(a_mat))).norm()} | must be < 1e-8") +# print(f"A * A.T() =\n{str(A*A.T())}must be\n{str(np.round(np.array(a_mat)@np.array(a_mat).T,decimals=3))}") +print( + f"Norm of (A*A.T() - np(A*A.T())) is {(A * A.T() - Matrix(np.array(a_mat) @ np.array(a_mat).T)).norm()} | must be < 1e-8") +# print(f"B.T() * A.T() =\n{str(B.T()*A.T())}must be\n{str(np.round(np.array(b_mat).T @ np.array(a_mat).T,decimals=3))}") +print( + f"Norm of (B.T()*A.T() - np.(B.T()*A.T())) is {(B.T() * A.T() - Matrix(np.array(b_mat).T @ np.array(a_mat).T)).norm()} | must be < 1e-8") +print("End 2e\n") + +### 1f divison +print("Start 2f divison") +# initialization +A = Matrix(a_mat) +# computation +print(f"Norm of (A/5 - np.(A/5)) is {(A / 5 - Matrix(np.array(a_mat) / 5)).norm()} | must be < 1e-8") +print("End 2f\n") + +### 1g norm +print("Start 2g norm") +A = Matrix(structure="tridiagonal", given_size=50, given_values=[-1, 2, -1]) +print(f"Frobenius norm of tridiagonal matrix: {A.norm('frobenius')} | must be 17.263") +print(f"Row sum norm of tridiagonal matrix: {A.norm('row sum')} | must be 2") +print(f"Col sum norm of tridiagonal matrix: {A.norm('col sum')} | must be 2") +print("End 2g\n") + +### 1h negation +print("Start 2h negation") +A = Matrix(structure="tridiagonal", given_size=50, given_values=[-1, 2, 1]) +print(f"Norm of (A + (-A)) is {(A + (-A)).norm('frobenius')} | must be < 1e-8") +print("End 2h\n") + +### 1i manipulation +print("Start 2i manipulation") +A = Matrix(structure="tridiagonal", given_size=10, given_values=[-1, 2, 1]) +A[1, 1] = 4 +A[[1, 2, 3], 2] = [-5, -10, 100] +print(str(A)) +print("End 2i\n") diff --git a/test/test_vector.py b/test/test_vector.py index 6b0f79f..7b2cc5c 100644 --- a/test/test_vector.py +++ b/test/test_vector.py @@ -22,6 +22,14 @@ class TestVector(TestCase): self.assertEqual(expected, actual) + def test_should_transpose_vector(self): + data = [1, 2, 3, 4, 5, 6] + actual = Vector(data) + + actual = actual.transpose() + expected = [[1, 2, 3, 4, 5, 6]] + self.assertEqual(expected, actual) + def test_should_neg_vector(self): v = Vector([1, 2]) @@ -96,12 +104,21 @@ class TestVector(TestCase): self.assertEqual(expected, actual) - def test_should_mul_vectors(self): + def test_should_mul_same_shape_vectors(self): + v1 = Vector([1, 2]) + v2 = Vector([3, 4]) + + expected = Vector([3, 8]) + actual = v1 * v2 + + self.assertEqual(expected, actual) + + def test_should_mul_vectors_dot(self): v1 = Vector([1, 2]) v2 = Vector([3, 4]) expected = 11 - actual = v1 * v2 + actual = v1.T() * v2 self.assertEqual(expected, actual)