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_2_in_1_ACA_with_identical_matrices(): n = 5 A = np.arange(1, 1 + n**2).reshape((n, n)) + np.random.rand(n, n) B = A.copy() def get_row_func(i): return A[i, :], B[i, :] def get_col_func(j): return A[:, j], B[:, j] lrA, lrB = LowRankMatrix.from_rows_and_cols_functions_with_multi_ACA( get_row_func, get_col_func, n, n, nb_matrices=2, tol=1e-3) assert (lrA.full_matrix() == lrB.full_matrix()).all() assert norm(lrA.full_matrix() - A, 'fro') / norm(A, 'fro') < 3e-2
def test_2_in_1_ACA_with_different_matrices(): n = 5 A = np.arange(1, 1 + n**2).reshape((n, n)) + np.random.rand(n, n) B = A.T def get_row_func(i): return A[i, :], B[i, :] def get_col_func(j): return A[:, j], B[:, j] lrA, lrB = LowRankMatrix.from_rows_and_cols_functions_with_multi_ACA( get_row_func, get_col_func, n, n, nb_matrices=2, max_rank=3) assert matrix_rank(lrA.full_matrix()) == matrix_rank( lrB.full_matrix()) == 3
def test_2_in_1_ACA_with_identical_matrices(): n = 5 A = np.fromfunction(lambda i, j: 1 / abs(i - (100 + j)), (n, n)) B = A.copy() def get_row_func(i): return A[i, :], B[i, :] def get_col_func(j): return A[:, j], B[:, j] lrA, lrB = LowRankMatrix.from_rows_and_cols_functions_with_multi_ACA( get_row_func, get_col_func, n, n, nb_matrices=2, tol=1e-3) assert (lrA.full_matrix() == lrB.full_matrix()).all() assert norm(lrA.full_matrix() - A, 'fro') / norm(A, 'fro') < 3e-2
def full_like(A, value, dtype=np.float64): """A matrix of the same kind and shape as A but filled with a single value.""" if isinstance(A, BlockMatrix): new_matrix = [] for i in range(A._stored_nb_blocks[0]): line = [] for j in range(A._stored_nb_blocks[1]): line.append( full_like(A._stored_blocks[i, j], value, dtype=dtype)) new_matrix.append(line) return A.__class__(new_matrix) elif isinstance(A, LowRankMatrix): return LowRankMatrix(np.ones((A.shape[0], 1)), np.full((1, A.shape[1]), value)) elif isinstance(A, np.ndarray): return np.full_like(A, value, dtype=dtype)
def test_low_rank_blocks(): n = 10 # Test initialization a, b = np.random.rand(n, 1), np.random.rand(1, n) LR = LowRankMatrix(a, b) assert LR.shape == LR.full_matrix().shape assert matrix_rank(LR.full_matrix()) == LR.rank == 1 assert LR.density == 2 * n / n**2 a, b = np.random.rand(n, 2), np.random.rand(2, n) LR = LowRankMatrix(a, b) assert LR.shape == LR.full_matrix().shape assert matrix_rank(LR.full_matrix()) == LR.rank == 2 # Test creation from SVD A = np.arange(n**2).reshape((n, n)) + 1.0 dumb_low_rank = LowRankMatrix.from_full_matrix_with_SVD(A, n) assert np.allclose(dumb_low_rank.full_matrix() - A, 0.0) A_rank_1 = LowRankMatrix.from_full_matrix_with_SVD(A, 1) assert matrix_rank(A_rank_1.full_matrix()) == A_rank_1.rank == 1 # Test recompression recompressed = dumb_low_rank.recompress(new_rank=2) assert recompressed.rank == matrix_rank(recompressed.full_matrix()) == 2 recompressed = dumb_low_rank.recompress(tol=1e-1) assert recompressed.rank <= dumb_low_rank.rank # Test multiplication with vector b = np.random.rand(n) assert np.allclose(A_rank_1 @ b, A_rank_1.full_matrix() @ b) # Test creation with ACA full_A_rank_1 = A_rank_1.full_matrix() A_rank_1_again = LowRankMatrix.from_full_matrix_with_ACA(full_A_rank_1, max_rank=5) assert matrix_rank(A_rank_1_again.full_matrix()) == 1 assert np.allclose(A_rank_1_again.full_matrix(), full_A_rank_1) # Test creation from function with ACA X = np.linspace(0, 1, n) Y = np.linspace(5, 6, 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)]) SLR = LowRankMatrix.from_function_with_ACA(f, n, n, max_rank=2, tol=1e-5) assert SLR.shape == (n, n) assert np.allclose(SLR.full_matrix(), S, atol=1e-4) summed = SLR + A_rank_1 assert summed.rank == 1
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 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)