From e6a9d5a2f0215667df08e3674f669c30e3be1431 Mon Sep 17 00:00:00 2001 From: Niklas Birk Date: Mon, 8 Apr 2024 00:26:20 +0200 Subject: [PATCH] Add distributed computing for negation and addition (no memory distributing) --- src/matrix.py | 23 ++++++++++- src/matrix_mpi.py | 98 ++++++++++++++++++++++++++++++++++++++++----- test/test_matrix.py | 9 +++++ 3 files changed, 119 insertions(+), 11 deletions(-) diff --git a/src/matrix.py b/src/matrix.py index 9e37544..eeeda3a 100644 --- a/src/matrix.py +++ b/src/matrix.py @@ -84,6 +84,25 @@ class Matrix: """ return self.__data__ + @staticmethod + def flatten(matrices: list): + """ + Flattens a list of matrices into one bigger matrix. + The columns must match the first ``Matrix`` in the list and the rows can be arbitrarily. + + :param matrices: A list of matrices. + :type matrices: list + :return: A ``Matrix`` extended by all matrices in the list. + :rtype: ``Matrix`` + """ + flattened_data = [] + rows = 0 + for matrix in matrices: + flattened_data.extend(matrix.get_data()) + rows += matrix.__shape__[0] + cols = matrices[0].__shape__[1] + return Matrix(flattened_data, (rows, cols)) + def shape(self): """ :return: the shape of the matrix, which is ``(rows, columns)`` @@ -219,7 +238,7 @@ class Matrix: def __rmul__(self, other): return self * other - def __abs_sum__(self): + def __abs_sum_of_squares__(self): rows = self.__shape__[0] cols = self.__shape__[1] abs_sum = 0 @@ -258,7 +277,7 @@ class Matrix: :return: the norm as a number """ if f == "frobenius": - norm = math.sqrt(self.__abs_sum__()) + norm = math.sqrt(self.__abs_sum_of_squares__()) elif f == "col sum": norm = max(self.__col_sums__()) elif f == "row sum": diff --git a/src/matrix_mpi.py b/src/matrix_mpi.py index 49eb7b1..ae424ea 100644 --- a/src/matrix_mpi.py +++ b/src/matrix_mpi.py @@ -5,9 +5,9 @@ from matrix import Matrix class MatrixMPI: - __comm__ = MPI.COMM_WORLD - __size__ = __comm__.Get_size() - __rank__ = __comm__.Get_rank() + __mpi_comm__ = MPI.COMM_WORLD + __mpi_size__ = __mpi_comm__.Get_size() + __mpi_rank__ = __mpi_comm__.Get_rank() __matrix__: Matrix = None __chunk__: list = None @@ -16,23 +16,103 @@ class MatrixMPI: self.__matrix__ = Matrix(data=data, shape=shape, structure=structure, model=model, offset=offset, n=n) total_amount_of_rows = self.__matrix__.shape()[0] - chunks = numpy.array_split(list(range(total_amount_of_rows)), self.__size__) - self.__chunk__ = chunks[self.__rank__].tolist() + chunks = numpy.array_split(list(range(total_amount_of_rows)), self.__mpi_size__) + self.__chunk__ = chunks[self.__mpi_rank__].tolist() + + @staticmethod + def of(matrix: Matrix): + return MatrixMPI(matrix.get_data(), matrix.shape()) def __str__(self): return str(self.__matrix__) - def get_rank_data(self): + def get_rank_submatrix(self): rows = len(self.__chunk__) cols = self.__matrix__.shape()[1] return Matrix(self.__matrix__[self.__chunk__], (rows, cols)) def transpose(self): - return self.__matrix__.transpose() + """ + :return: the transpose of the matrix + """ + return MatrixMPI.of(self.__matrix__.transpose()) def T(self): + """ + Same as ``matrix.transpose()`` + + :return: see ``matrix.transpose()`` + """ return self.transpose() + def __eq__(self, other): + """ + Return ``self==value`` -mpi_matrix = MatrixMPI(list(range(1, 25)), (12, 2)) -print(mpi_matrix.transpose()) + :param other: The object to compare to; must be either a ``MatrixMPI``, ``Matrix``, a ``list`` or a ``numpy.ndarray`` + :return: True if data in the matrix are equal to the given data in other for each component, otherwise False + """ + if isinstance(other, MatrixMPI): + return self.__matrix__ == other.__matrix__ + else: + return self.__matrix__ == other + + def __neg__(self): + gathered_data = self.__mpi_comm__.gather(-self.get_rank_submatrix()) + data = self.__mpi_comm__.bcast(gathered_data) + return MatrixMPI.of(Matrix.flatten(data)) + + def __add__(self, other): + if isinstance(other, MatrixMPI): + other = other.get_rank_submatrix() + gathered_data = self.__mpi_comm__.gather(self.get_rank_submatrix() + other) + data = self.__mpi_comm__.bcast(gathered_data) + return MatrixMPI.of(Matrix.flatten(data)) + + def __radd__(self, other): + return self + other + + def __sub__(self, other): + return self + (-other) + + def __rsub__(self, other): + return -self + other + + def __truediv__(self, other): + gathered_data = self.__mpi_comm__.gather(self.get_rank_submatrix() / other) + data = self.__mpi_comm__.bcast(gathered_data) + return MatrixMPI.of(Matrix.flatten(data)) + + def __mul__(self, other): + if isinstance(other, MatrixMPI): + return self.__matrix__ * other.__matrix__ + else: + return self.__matrix__ * other + + def __rmul__(self, other): + return self * other + + def norm(self, f: str = "frobenius"): + """ + Calculates the norm of the matrix. + + A norm is a positive definit, absolute homogeneous and subadditive function. + For Matrices a norm is also sub-multiplicative. + + :param f: The norm to be used, could be either "frobenius", "row sum" or "col sum" + + :return: the norm as a number + """ + return self.__matrix__.norm(f) + + def __getitem__(self, key): + return self.__matrix__[key] + + def __setitem__(self, key, value): + self.__matrix__[key] = value + + +# m1 = MatrixMPI(numpy.random.uniform(0, 1, 1_000_000), (1000, 1000)) +m1 = MatrixMPI([1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6], (5, 3)) + +print(m1 - m1) diff --git a/test/test_matrix.py b/test/test_matrix.py index 071e5d7..a41bb28 100644 --- a/test/test_matrix.py +++ b/test/test_matrix.py @@ -378,3 +378,12 @@ class TestMatrix(TestCase): expected = Matrix([1, 2, 3, 4, 5, 60, 70, 8, 9, 100, 110, 12, 13, 14, 15, 16], (4, 4)) self.assertEqual(expected, actual) + + def test_should_flatten_list_of_matrices(self): + m1 = Matrix([1, 2, 3, 4, 5, 6, 7, 8, 9], (3, 3)) + m2 = Matrix([1, 2, 3, 4, 5, 6], (2, 3)) + + actual = m1.flatten([m1, m2]) + expected = Matrix([1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6], (5, 3)) + + self.assertEqual(expected, actual)