def test_main_procedure(self): for n in self.test_dimensions: # Obtain a random quadratic Hamiltonian quadratic_hamiltonian = random_quadratic_hamiltonian(n) # Get the diagonalizing transformation transformation_matrix = ( quadratic_hamiltonian.diagonalizing_bogoliubov_transform()) left_block = transformation_matrix[:, :n] right_block = transformation_matrix[:, n:] lower_unitary = numpy.empty((n, 2 * n), dtype=complex) lower_unitary[:, :n] = numpy.conjugate(right_block) lower_unitary[:, n:] = numpy.conjugate(left_block) # Get fermionic Gaussian decomposition of lower_unitary decomposition, left_decomposition, diagonal, left_diagonal = ( fermionic_gaussian_decomposition(lower_unitary)) # Compute left_unitary left_unitary = numpy.eye(n, dtype=complex) for parallel_set in left_decomposition: combined_op = numpy.eye(n, dtype=complex) for op in reversed(parallel_set): i, j, theta, phi = op c = numpy.cos(theta) s = numpy.sin(theta) phase = numpy.exp(1.j * phi) givens_rotation = numpy.array( [[c, -phase * s], [s, phase * c]], dtype=complex) givens_rotate(combined_op, givens_rotation, i, j) left_unitary = combined_op.dot(left_unitary) for i in range(n): left_unitary[i] *= left_diagonal[i] left_unitary = left_unitary.T for i in range(n): left_unitary[i] *= diagonal[i] # Check that left_unitary zeroes out the correct entries of # lower_unitary product = left_unitary.dot(lower_unitary) for i in range(n - 1): for j in range(n - 1 - i): self.assertAlmostEqual(product[i, j], 0.) # Compute right_unitary right_unitary = numpy.eye(2 * n, dtype=complex) for parallel_set in decomposition: combined_op = numpy.eye(2 * n, dtype=complex) for op in reversed(parallel_set): if op == 'pht': swap_rows(combined_op, n - 1, 2 * n - 1) else: i, j, theta, phi = op c = numpy.cos(theta) s = numpy.sin(theta) phase = numpy.exp(1.j * phi) givens_rotation = numpy.array( [[c, -phase * s], [s, phase * c]], dtype=complex) double_givens_rotate(combined_op, givens_rotation, i, j) right_unitary = combined_op.dot(right_unitary) # Compute left_unitary * lower_unitary * right_unitary^\dagger product = left_unitary.dot(lower_unitary.dot( right_unitary.T.conj())) # Construct the diagonal matrix diag = numpy.zeros((n, 2 * n), dtype=complex) diag[range(n), range(n, 2 * n)] = diagonal # Assert that W and D are the same for i in numpy.ndindex((n, 2 * n)): self.assertAlmostEqual(diag[i], product[i])
def antisymmetric_canonical_form(antisymmetric_matrix): """Compute the canonical form of an antisymmetric matrix. The input is a real, antisymmetric n x n matrix A, where n is even. Its canonical form is:: A = R^T C R where R is a real, orthogonal matrix and C has the form:: [ 0 D ] [ -D 0 ] where D is a diagonal matrix with nonnegative entries. Args: antisymmetric_matrix(ndarray): An antisymmetric matrix with even dimension. Returns: canonical(ndarray): The canonical form C of antisymmetric_matrix orthogonal(ndarray): The orthogonal transformation R. """ m, p = antisymmetric_matrix.shape if m != p or p % 2 != 0: raise ValueError('The input matrix must be square with even ' 'dimension.') # Check that input matrix is antisymmetric matrix_plus_transpose = antisymmetric_matrix + antisymmetric_matrix.T maxval = numpy.max(numpy.abs(matrix_plus_transpose)) if maxval > EQ_TOLERANCE: raise ValueError('The input matrix must be antisymmetric.') # Compute Schur decomposition canonical, orthogonal = schur(antisymmetric_matrix, output='real') # The returned form is block diagonal; we need to permute rows and columns # to put it into the form we want n = p // 2 for i in range(1, n, 2): swap_rows(canonical, i, n + i - 1) swap_columns(canonical, i, n + i - 1) swap_columns(orthogonal, i, n + i - 1) if n % 2 != 0: swap_rows(canonical, n - 1, n + i) swap_columns(canonical, n - 1, n + i) swap_columns(orthogonal, n - 1, n + i) # Now we permute so that the upper right block is non-negative for i in range(n): if canonical[i, n + i] < -EQ_TOLERANCE: swap_rows(canonical, i, n + i) swap_columns(canonical, i, n + i) swap_columns(orthogonal, i, n + i) # Now we permute so that the nonzero entries are ordered by magnitude # We use insertion sort diagonal = canonical[range(n), range(n, 2 * n)] for i in range(n): # Insert the smallest element from the unsorted part of the list into # index i arg_min = numpy.argmin(diagonal[i:]) + i if arg_min != i: # Permute the upper right block swap_rows(canonical, i, arg_min) swap_columns(canonical, n + i, n + arg_min) swap_columns(orthogonal, n + i, n + arg_min) # Permute the lower left block swap_rows(canonical, n + i, n + arg_min) swap_columns(canonical, i, arg_min) swap_columns(orthogonal, i, arg_min) # Update diagonal swap_rows(diagonal, i, arg_min) return canonical, orthogonal.T