def test_hierarchical_matrix(): n = 30 X = np.linspace(0, 1, n) Y = np.linspace(10, 11, n) def f(i, j): return 1 / abs(X[i] - Y[j]) S = np.array([[f(i, j) for j in range(n)] for i in range(n)]) HS = BlockMatrix([ [ S[:n // 2, :n // 2], LowRankMatrix.from_full_matrix_with_ACA(S[n // 2:, :n // 2], tol=1e-5) ], [ LowRankMatrix.from_full_matrix_with_ACA(S[:n // 2, n // 2:], tol=1e-5), S[n // 2:, n // 2:] ], ]) assert np.allclose(HS.full_matrix(), S, rtol=2e-1) doubled = HS + HS assert np.allclose(2 * S, doubled.full_matrix(), rtol=2e-1)
def test_block_matrix_representation_of_identity(): # 2x2 block representation of the identity matrix A = BlockMatrix([[np.eye(2, 2), np.zeros((2, 2))], [np.zeros((2, 2)), np.eye(2, 2)]]) assert A.shape == (4, 4) assert A.nb_blocks == (2, 2) assert A.block_shapes == ([2, 2], [2, 2]) assert list(A._stored_block_positions()) == [[(0, 0)], [(0, 2)], [(2, 0)], [(2, 2)]] assert A.str_shape == "2×2×[2×2]" assert A.density == 1.0 assert ((A + A) / 2 == A).all() assert (-A).min() == -1 assert (2 * A).max() == 2 assert (A * A == A).all() b = np.random.rand(4) assert (A @ b == b).all() assert (A @ A == A).all() patches = A._patches(global_frame=(10, 10)) assert {rectangle.get_xy() for rectangle in patches} == {(10, 10), (12, 10), (10, 12), (12, 12)} assert (A == identity_like(A)).all() assert (A.full_matrix() == np.eye(4, 4)).all() # 2x2 block matrix containing one another block matrix and three regular matrices of different shapes B = BlockMatrix([[A, np.zeros((4, 1))], [np.zeros((1, 4)), np.ones((1, 1))]]) assert B.shape == (5, 5) assert B.block_shapes == ([4, 1], [4, 1]) assert (B.full_matrix() == np.eye(5, 5)).all() assert B.str_shape == "[[2×2×[2×2], 4×1], [1×4, 1×1]]" assert B.block_shapes == ([4, 1], [4, 1]) assert list(B._stored_block_positions()) == [[(0, 0)], [(0, 4)], [(4, 0)], [(4, 4)]] patches = B._patches(global_frame=(10, 10)) assert {rectangle.get_xy() for rectangle in patches} == {(10, 10), (12, 10), (10, 12), (12, 12), (14, 10), (10, 14), (14, 14)} # 3x3 block matrix with blocks of different shapes C = random_block_matrix([1, 2, 4], [1, 2, 4]) assert C.nb_blocks == (3, 3) assert C.block_shapes == ([1, 2, 4], [1, 2, 4]) assert (ones_like(C).full_matrix() == np.ones(C.shape)).all() assert (cut_matrix(C.full_matrix(), *C.block_shapes) == C).all() assert (C @ random_block_matrix(C.block_shapes[1], [6])).block_shapes == ([ 1, 2, 4 ], [6]) b = np.random.rand(7) assert np.allclose(C @ b, C.full_matrix() @ b)
def test_block_circulant_matrix(): # 5x5 block symmetric circulant matrix reprensentation of the identity matrix A = BlockCirculantMatrix( [[np.eye(2, 2), np.zeros((2, 2)), np.zeros((2, 2))]]) assert A.nb_blocks == (3, 3) assert A.shape == (6, 6) assert A.block_shapes == ([2, 2, 2], [2, 2, 2]) assert (A.full_matrix() == np.eye(*A.shape)).all() assert ((A + A) / 2 == A).all() assert (-A).min() == -1 assert (2 * A).max() == 2 assert (A * A == A).all() b = np.random.rand(A.shape[0]) assert np.allclose(A @ b, A.full_matrix() @ b) assert np.allclose(A.rmatvec(b), b @ A.full_matrix()) # Nested matrix B = BlockCirculantMatrix([[A, 2 * A, 3 * A]]) assert B.nb_blocks == (3, 3) assert B.shape == (18, 18) assert (B.all_blocks[0, 0] == A).all() C = BlockCirculantMatrix([[np.array([[i]]) for i in range(5)]]) b = np.random.rand(C.shape[0]) assert np.allclose(BlockMatrix.matvec(C, b), C.full_matrix() @ b) assert np.allclose(C @ b, C.full_matrix() @ b) assert np.allclose(C.rmatvec(b), b @ C.full_matrix())
def cut_matrix(full_matrix, x_shapes, y_shapes, check=False): """Transform a numpy array into a block matrix of numpy arrays. Parameters ---------- full_matrix: numpy array The matrix to split into blocks. x_shapes: sequence of int The columns at which to split the blocks. y_shapes: sequence of int The lines at which to split the blocks. check: bool, optional Check to dimensions and type of the matrix after creation (default: False). Return ------ BlockMatrix The same matrix as the input one but in block form. """ new_block_matrix = [] for i, di in zip(accumulate([0] + x_shapes[:-1]), x_shapes): line = [] for j, dj in zip(accumulate([0] + x_shapes[:-1]), y_shapes): line.append(full_matrix[i:i + di, j:j + dj]) new_block_matrix.append(line) return BlockMatrix(new_block_matrix, check=check)
def block_diagonalize(self): """Returns an array of matrices""" if not hasattr(self, 'block_diagonalization'): if all(isinstance(matrix, BlockMatrix) for matrix in self._stored_blocks[0, :]): self.block_diagonalization = BlockMatrix.fft_of_list(*self.all_blocks[:, 0]) else: stacked_blocks = np.empty((self.nb_blocks[1],) + self.block_shape, dtype=self.dtype) for i, block in enumerate(self.all_blocks[:, 0]): stacked_blocks[i] = block.full_matrix() if not isinstance(block, np.ndarray) else block self.block_diagonalization = np.fft.fft(stacked_blocks, axis=0) return self.block_diagonalization
def test_solve_block_toeplitz(): A = BlockToeplitzMatrix([[(lambda: np.random.rand(1, 1))() for _ in range(7)]]) b = np.random.rand(A.shape[0]) assert np.allclose(BlockMatrix.matvec(A, b), A.full_matrix() @ b) assert np.allclose(A @ b, A.full_matrix() @ b) x_gmres = solve_gmres(A, b) x_dumb_gmres = solve_gmres(A.full_matrix(), b) assert np.allclose(x_gmres, x_dumb_gmres, rtol=1e-6)
def test_block_symmetric_toeplitz_matrices(): # 2x2 block symmetric Toeplitz representation of the identity matrix A = BlockSymmetricToeplitzMatrix([[np.eye(2, 2), np.zeros((2, 2))]]) assert A.nb_blocks == (2, 2) assert A.block_shapes == ([2, 2], [2, 2]) assert A.shape == (4, 4) assert A.block_shape == (2, 2) assert list(A._stored_block_positions()) == [[(0, 0), (2, 2)], [(0, 2), (2, 0)]] assert (A.full_matrix() == np.eye(*A.shape)).all() assert ((A + A) / 2 == A).all() assert (-A).min() == -1 assert (2 * A).max() == 2 assert (A * A == A).all() b = np.random.rand(4) assert np.allclose(A @ b, b) assert np.allclose(A.rmatvec(b), b) # The same as a simpler block matrix A2 = BlockMatrix(A.all_blocks) assert (A2.full_matrix() == A.full_matrix()).all() # 2x2 blocks symmetric Toeplitz matrix of non-squared matrix A3 = BlockSymmetricToeplitzMatrix([[np.ones((3, 1)), np.zeros((3, 1))]]) assert A3.nb_blocks == (2, 2) assert A3.block_shapes == ([3, 3], [1, 1]) assert A3.shape == (6, 2) assert A3.block_shape == (3, 1) with pytest.raises(AssertionError): BlockSymmetricToeplitzMatrix([[np.ones((1, 1)), np.zeros((2, 2))]]) # 2x2 block symmetrix Toeplitz matrix composed of block matrices B = BlockSymmetricToeplitzMatrix([[ random_block_matrix([1, 1], [1, 1]), random_block_matrix([1, 1], [1, 1]) ]]) assert B.nb_blocks == (2, 2) assert B._stored_nb_blocks == (1, 2) assert B.block_shapes == ([2, 2], [2, 2]) assert B.block_shape == (2, 2) assert B.shape == (4, 4) assert isinstance(B.all_blocks[0, 0], BlockMatrix) assert (B.all_blocks[0, 0] == B.all_blocks[1, 1]).all() list_of_matrices = [B, B + ones_like(B), B, B - ones_like(B)] BB_fft = BlockSymmetricToeplitzMatrix.fft_of_list(*list_of_matrices) full_BB_fft = np.fft.fft(np.array( [A.full_matrix() for A in list_of_matrices]), axis=0) assert np.allclose(full_BB_fft, np.array([A.full_matrix() for A in BB_fft])) # 2x2 block symmetric Toeplitz matrix composed of the previous block symmetric Toeplitz matrices C = BlockSymmetricToeplitzMatrix([[A, B]]) assert C.shape == (8, 8) b = np.random.rand(8) assert np.allclose(C @ b, C.full_matrix() @ b) assert np.allclose(C.rmatvec(b), b @ C.full_matrix()) # We need to go deeper D = BlockMatrix([[C, np.zeros(C.shape)]]) assert D.shape == (8, 16) b = np.random.rand(16) assert np.allclose(D @ b, D.full_matrix() @ b)
def test_block_toeplitz_matrices(): # 2x2 block Toeplitz representation of the identity matrix A = BlockToeplitzMatrix( [[np.eye(2, 2), np.zeros((2, 2)), np.zeros((2, 2))]]) assert A.block_shapes == ([2, 2], [2, 2]) assert A.block_shape == (2, 2) assert A.shape == (4, 4) assert list(A._stored_block_positions()) == [[(0, 0), (2, 2)], [(0, 2)], [(2, 0)]] assert (A.full_matrix() == np.eye(*A.shape)).all() # Removing the Toeplitz structure. Ant = A.no_toeplitz() assert not isinstance(Ant, BlockToeplitzMatrix) assert Ant.all_blocks[0, 0] is Ant.all_blocks[1, 1] # When using no_toeplitz, the resulting BlockMatrix still contains several references to the same array object. from copy import deepcopy Ant_cp = deepcopy(Ant) assert Ant_cp.all_blocks[0, 0] is not Ant_cp.all_blocks[1, 1] # The deepcopy has made different copies of the blocks in the block matrix without explicit Toeplitz structure. # However, A_cp = deepcopy(A) assert A_cp.all_blocks[0, 0] is A_cp.all_blocks[1, 1] # The matrix with explicit Toeplitz structure still keep the structure when deepcopied. assert ((A + A) / 2 == A).all() assert (-A).min() == -1 assert (2 * A).max() == 2 assert (A * A == A).all() assert isinstance(A.circulant_super_matrix, BlockCirculantMatrix) assert A.circulant_super_matrix.shape == (6, 6) b = np.random.rand(A.shape[0]) assert np.allclose(A @ b, b) assert np.allclose(A.rmatvec(b), b) # 3x3 block random Toeplitz matrix B = BlockToeplitzMatrix([[ random_block_matrix([1], [1]), random_block_matrix([1], [1]), random_block_matrix([1], [1]), random_block_matrix([1], [1]), random_block_matrix([1], [1]), ]]) assert B.shape == (3, 3) assert B.nb_blocks == (3, 3) assert B.block_shape == (1, 1) assert list(B._stored_block_positions()) == [[(0, 0), (1, 1), (2, 2)], [(0, 1), (1, 2)], [(0, 2)], [(2, 0)], [(1, 0), (2, 1)]] assert isinstance(B.circulant_super_matrix, BlockCirculantMatrix) assert B.circulant_super_matrix.shape == (5, 5) b = np.random.rand(B.shape[0]) assert np.allclose(BlockMatrix.matvec(B, b), B.full_matrix() @ b) assert np.allclose(B @ b, B.full_matrix() @ b) assert np.allclose(B.rmatvec(b), b @ B.full_matrix())
def build_matrices(self, mesh1, mesh2, *args, _rec_depth=1, **kwargs): """Recursively builds a hierarchical matrix between mesh1 and mesh2. Same arguments as :func:`BasicMatrixEngine.build_matrices`. :code:`_rec_depth` keeps track of the recursion depth only for pretty log printing. """ if logging.getLogger().isEnabledFor(logging.DEBUG): log_entry = ( "\t" * (_rec_depth + 1) + "Build the S and K influence matrices between {mesh1} and {mesh2}" .format( mesh1=mesh1.name, mesh2=(mesh2.name if mesh2 is not mesh1 else 'itself'))) else: log_entry = "" # will not be used green_function = args[-1] # Distance between the meshes (for ACA). distance = np.linalg.norm(mesh1.center_of_mass_of_nodes - mesh2.center_of_mass_of_nodes) # I) SPARSE COMPUTATION # I-i) BLOCK TOEPLITZ MATRIX if (isinstance(mesh1, ReflectionSymmetricMesh) and isinstance(mesh2, ReflectionSymmetricMesh) and mesh1.plane == mesh2.plane): LOG.debug(log_entry + " using mirror symmetry.") S_a, V_a = self.build_matrices(mesh1[0], mesh2[0], *args, **kwargs, _rec_depth=_rec_depth + 1) S_b, V_b = self.build_matrices(mesh1[0], mesh2[1], *args, **kwargs, _rec_depth=_rec_depth + 1) return BlockSymmetricToeplitzMatrix( [[S_a, S_b]]), BlockSymmetricToeplitzMatrix([[V_a, V_b]]) elif (isinstance(mesh1, TranslationalSymmetricMesh) and isinstance(mesh2, TranslationalSymmetricMesh) and np.allclose(mesh1.translation, mesh2.translation) and mesh1.nb_submeshes == mesh2.nb_submeshes): LOG.debug(log_entry + " using translational symmetry.") S_list, V_list = [], [] for submesh in mesh2: S, V = self.build_matrices(mesh1[0], submesh, *args, **kwargs, _rec_depth=_rec_depth + 1) S_list.append(S) V_list.append(V) for submesh in mesh1[1:][::-1]: S, V = self.build_matrices(submesh, mesh2[0], *args, **kwargs, _rec_depth=_rec_depth + 1) S_list.append(S) V_list.append(V) return BlockToeplitzMatrix([S_list]), BlockToeplitzMatrix([V_list]) elif (isinstance(mesh1, AxialSymmetricMesh) and isinstance(mesh2, AxialSymmetricMesh) and mesh1.axis == mesh2.axis and mesh1.nb_submeshes == mesh2.nb_submeshes): LOG.debug(log_entry + " using rotation symmetry.") S_line, V_line = [], [] for submesh in mesh2[:mesh2.nb_submeshes]: S, V = self.build_matrices(mesh1[0], submesh, *args, **kwargs, _rec_depth=_rec_depth + 1) S_line.append(S) V_line.append(V) return BlockCirculantMatrix([S_line ]), BlockCirculantMatrix([V_line]) # I-ii) LOW-RANK MATRIX WITH ACA elif distance > self.ACA_distance * mesh1.diameter_of_nodes or distance > self.ACA_distance * mesh2.diameter_of_nodes: LOG.debug(log_entry + " using ACA.") def get_row_func(i): s, v = green_function.evaluate(mesh1.extract_one_face(i), mesh2, *args[:-1], **kwargs) return s.flatten(), v.flatten() def get_col_func(j): s, v = green_function.evaluate(mesh1, mesh2.extract_one_face(j), *args[:-1], **kwargs) return s.flatten(), v.flatten() return LowRankMatrix.from_rows_and_cols_functions_with_multi_ACA( get_row_func, get_col_func, mesh1.nb_faces, mesh2.nb_faces, nb_matrices=2, id_main= 1, # Approximate V and get an approximation of S at the same time tol=self.ACA_tol, dtype=np.complex128) # II) NON-SPARSE COMPUTATIONS # II-i) BLOCK MATRIX elif (isinstance(mesh1, CollectionOfMeshes) and isinstance(mesh2, CollectionOfMeshes)): LOG.debug(log_entry + " using block matrix structure.") S_matrix, V_matrix = [], [] for submesh1 in mesh1: S_line, V_line = [], [] for submesh2 in mesh2: S, V = self.build_matrices(submesh1, submesh2, *args, **kwargs, _rec_depth=_rec_depth + 1) S_line.append(S) V_line.append(V) S_matrix.append(S_line) V_matrix.append(V_line) return BlockMatrix(S_matrix), BlockMatrix(V_matrix) # II-ii) PLAIN NUMPY ARRAY else: LOG.debug(log_entry) S, V = green_function.evaluate(mesh1, mesh2, *args[:-1], **kwargs) return S, V
def block_three_rect(two_by_two_block_identity): A = two_by_two_block_identity B = BlockMatrix([[A, np.zeros((4, 1))], [np.zeros((1, 4)), np.ones((1, 1))]]) return B
def two_by_two_block_identity(): A = BlockMatrix([[np.eye(2, 2), np.zeros((2, 2))], [np.zeros((2, 2)), np.eye(2, 2)]]) return A
def build_hierarchical_toeplitz_matrix(mesh1, mesh2, *args, _rec_depth=1, **kwargs): """Assemble hierarchical Toeplitz matrices. The method is basically an ugly multiple dispatch on the kind of mesh. For hierarchical structures, the method is called recursively on all of the sub-bodies. Parameters ---------- mesh1: Mesh or CollectionOfMeshes mesh of the receiving body (where the potential is measured) mesh2: Mesh or CollectionOfMeshes mesh of the source body (over which the source distribution is integrated) *args, **kwargs Other arguments, passed to the actual evaluation of the coefficients _rec_depth: int, optional internal parameter: recursion accumulator, used only for pretty logging Returns ------- pair of matrix-like objects influence matrices """ if logging.getLogger().isEnabledFor(logging.DEBUG): log_entry = "\t" * ( _rec_depth + 1) + function_description_for_logging.format( mesh1=mesh1.name, mesh2=(mesh2.name if mesh2 is not mesh1 else 'itself')) else: log_entry = "" # irrelevant # Distance between the meshes (for ACA). distance = np.linalg.norm(mesh1.center_of_mass_of_nodes - mesh2.center_of_mass_of_nodes) # I) SPARSE COMPUTATION if (isinstance(mesh1, ReflectionSymmetricMesh) and isinstance(mesh2, ReflectionSymmetricMesh) and mesh1.plane == mesh2.plane): LOG.debug(log_entry + " using mirror symmetry.") S_a, V_a = build_hierarchical_toeplitz_matrix( mesh1[0], mesh2[0], *args, **kwargs, _rec_depth=_rec_depth + 1) S_b, V_b = build_hierarchical_toeplitz_matrix( mesh1[0], mesh2[1], *args, **kwargs, _rec_depth=_rec_depth + 1) return BlockSymmetricToeplitzMatrix( [[S_a, S_b]]), BlockSymmetricToeplitzMatrix([[V_a, V_b]]) elif (isinstance(mesh1, TranslationalSymmetricMesh) and isinstance(mesh2, TranslationalSymmetricMesh) and np.allclose(mesh1.translation, mesh2.translation) and mesh1.nb_submeshes == mesh2.nb_submeshes): LOG.debug(log_entry + " using translational symmetry.") S_list, V_list = [], [] for submesh in mesh2: S, V = build_hierarchical_toeplitz_matrix( mesh1[0], submesh, *args, **kwargs, _rec_depth=_rec_depth + 1) S_list.append(S) V_list.append(V) for submesh in mesh1[1:][::-1]: S, V = build_hierarchical_toeplitz_matrix( submesh, mesh2[0], *args, **kwargs, _rec_depth=_rec_depth + 1) S_list.append(S) V_list.append(V) return BlockToeplitzMatrix([S_list]), BlockToeplitzMatrix([V_list]) elif (isinstance(mesh1, AxialSymmetricMesh) and isinstance(mesh2, AxialSymmetricMesh) and mesh1.axis == mesh2.axis and mesh1.nb_submeshes == mesh2.nb_submeshes): LOG.debug(log_entry + " using rotation symmetry.") S_line, V_line = [], [] for submesh in mesh2[:mesh2.nb_submeshes]: S, V = build_hierarchical_toeplitz_matrix( mesh1[0], submesh, *args, **kwargs, _rec_depth=_rec_depth + 1) S_line.append(S) V_line.append(V) return BlockCirculantMatrix([S_line ]), BlockCirculantMatrix([V_line]) elif distance > ACA_distance * mesh1.diameter_of_nodes or distance > ACA_distance * mesh2.diameter_of_nodes: # Low-rank matrix computed with Adaptive Cross Approximation. LOG.debug(log_entry + " using ACA.") def get_row_func(i): s, v = build_matrices(mesh1.extract_one_face(i), mesh2, *args, **kwargs) return s.flatten(), v.flatten() def get_col_func(j): s, v = build_matrices(mesh1, mesh2.extract_one_face(j), *args, **kwargs) return s.flatten(), v.flatten() return LowRankMatrix.from_rows_and_cols_functions_with_multi_ACA( get_row_func, get_col_func, mesh1.nb_faces, mesh2.nb_faces, nb_matrices=2, id_main= 1, # Approximate V and get an approximation of S at the same time tol=ACA_tol, dtype=dtype) # II) NON-SPARSE COMPUTATIONS elif (isinstance(mesh1, CollectionOfMeshes) and isinstance(mesh2, CollectionOfMeshes)): # Recursively build a block matrix LOG.debug(log_entry + " using block matrix structure.") S_matrix, V_matrix = [], [] for submesh1 in mesh1: S_line, V_line = [], [] for submesh2 in mesh2: S, V = build_hierarchical_toeplitz_matrix( submesh1, submesh2, *args, **kwargs, _rec_depth=_rec_depth + 1) S_line.append(S) V_line.append(V) S_matrix.append(S_line) V_matrix.append(V_line) return BlockMatrix(S_matrix), BlockMatrix(V_matrix) else: # Actual evaluation of coefficients using the Green function. LOG.debug(log_entry) return build_matrices(mesh1, mesh2, *args, **kwargs)