def test_square(self): m, n = (3, 3) # Obtain a random matrix of orthonormal rows Q = random_unitary_matrix(n) Q = Q[:m, :] Q = Q[:m, :] # Get Givens decomposition of Q givens_rotations, V, diagonal = givens_decomposition(Q) # There should be no Givens rotations self.assertEqual(givens_rotations, list()) # Compute V * Q * U^\dagger W = V.dot(Q) # Construct the diagonal matrix D = numpy.zeros((m, n), dtype=complex) D[numpy.diag_indices(m)] = diagonal # Assert that W and D are the same for i in range(m): for j in range(n): self.assertAlmostEqual(D[i, j], W[i, j])
def test_real_numbers(self): for m, n in self.test_dimensions: # Obtain a random real matrix of orthonormal rows Q = random_unitary_matrix(n, real=True) Q = Q[:m, :] # Get Givens decomposition of Q givens_rotations, V, diagonal = givens_decomposition(Q) # Compute U U = numpy.eye(n, dtype=complex) for parallel_set in givens_rotations: combined_givens = numpy.eye(n, dtype=complex) for i, j, theta, phi in reversed(parallel_set): c = numpy.cos(theta) s = numpy.sin(theta) phase = numpy.exp(1.j * phi) G = numpy.array([[c, -phase * s], [s, phase * c]], dtype=complex) givens_rotate(combined_givens, G, i, j) U = combined_givens.dot(U) # Compute V * Q * U^\dagger W = V.dot(Q.dot(U.T.conj())) # Construct the diagonal matrix D = numpy.zeros((m, n), dtype=complex) D[numpy.diag_indices(m)] = diagonal # Assert that W and D are the same for i in range(m): for j in range(n): self.assertAlmostEqual(D[i, j], W[i, j])
def test_bad_dimensions(self): m, n = (3, 2) # Obtain a random matrix of orthonormal rows Q = random_unitary_matrix(m) Q = Q[:m, :] Q = Q[:m, :n] with self.assertRaises(ValueError): _ = givens_decomposition(Q)
def test_identity(self): n = 3 Q = numpy.eye(n, dtype=complex) givens_rotations, V, diagonal = givens_decomposition(Q) # V should be the identity identity = numpy.eye(n, dtype=complex) for i in range(n): for j in range(n): self.assertAlmostEqual(V[i, j], identity[i, j]) # There should be no Givens rotations self.assertEqual(givens_rotations, list()) # The diagonal should be ones for d in diagonal: self.assertAlmostEqual(d, 1.)
def test_antidiagonal(self): m, n = (3, 3) Q = numpy.zeros((m, n), dtype=complex) Q[0, 2] = 1. Q[1, 1] = 1. Q[2, 0] = 1. givens_rotations, V, diagonal = givens_decomposition(Q) # There should be no Givens rotations self.assertEqual(givens_rotations, list()) # VQ should equal the diagonal VQ = V.dot(Q) D = numpy.zeros((m, n), dtype=complex) D[numpy.diag_indices(m)] = diagonal for i in range(n): for j in range(n): self.assertAlmostEqual(VQ[i, j], D[i, j])
def slater_determinant_preparation_circuit(slater_determinant_matrix): r"""Obtain the description of a circuit which prepares a Slater determinant. The input is an :math:`N_f \times N` matrix :math:`Q` with orthonormal rows. Such a matrix describes the Slater determinant .. math:: b^\dagger_1 \cdots b^\dagger_{N_f} \lvert \text{vac} \rangle, where .. math:: b^\dagger_j = \sum_{k = 1}^N Q_{jk} a^\dagger_k. The output is the description of a circuit which prepares this Slater determinant, up to a global phase. The starting state which the circuit should be applied to is a Slater determinant (in the computational basis) with the first :math:`N_f` orbitals filled. Args: slater_determinant_matrix: The matrix :math:`Q` which describes the Slater determinant to be prepared. Returns: circuit_description: A list of operations describing the circuit. Each operation is a tuple of elementary operations that can be performed in parallel. Each elementary operation is a tuple of the form :math:`(i, j, \theta, \varphi)`, indicating a Givens rotation of modes :math:`i` and :math:`j` by angles :math:`\theta` and :math:`\varphi`. """ decomposition, _, _ = givens_decomposition(slater_determinant_matrix) circuit_description = list(reversed(decomposition)) return circuit_description
def test_forced_insertion(self): Q = numpy.zeros([2, 4]) Q[0, 0] = Q[1, 1] = 1 givens_rotations, _, _ = givens_decomposition(Q, always_insert=True) assert len(givens_rotations) == 3