def test_random_operators_are_reproducible(self): op1 = random_diagonal_coulomb_hamiltonian(5, seed=5947) op2 = random_diagonal_coulomb_hamiltonian(5, seed=5947) numpy.testing.assert_allclose(op1.one_body, op2.one_body) numpy.testing.assert_allclose(op1.two_body, op2.two_body) op1 = random_interaction_operator(5, seed=8911) op2 = random_interaction_operator(5, seed=8911) numpy.testing.assert_allclose(op1.one_body_tensor, op2.one_body_tensor) numpy.testing.assert_allclose(op1.two_body_tensor, op2.two_body_tensor) op1 = random_quadratic_hamiltonian(5, seed=17711) op2 = random_quadratic_hamiltonian(5, seed=17711) numpy.testing.assert_allclose(op1.combined_hermitian_part, op2.combined_hermitian_part) numpy.testing.assert_allclose(op1.antisymmetric_part, op2.antisymmetric_part) op1 = random_antisymmetric_matrix(5, seed=24074) op2 = random_antisymmetric_matrix(5, seed=24074) numpy.testing.assert_allclose(op1, op2) op1 = random_hermitian_matrix(5, seed=56753) op2 = random_hermitian_matrix(5, seed=56753) numpy.testing.assert_allclose(op1, op2) op1 = random_unitary_matrix(5, seed=56486) op2 = random_unitary_matrix(5, seed=56486) numpy.testing.assert_allclose(op1, op2)
def test_random_quadratic(self): n_qubits = 5 quad_ham = random_quadratic_hamiltonian(n_qubits, True) ferm_op = get_fermion_operator(quad_ham) self.assertTrue( jordan_wigner(ferm_op) == jordan_wigner( get_diagonal_coulomb_hamiltonian(ferm_op)))
def test_prepare_gaussian_state(n_qubits, conserves_particle_number, occupied_orbitals, initial_state, atol=1e-5): qubits = LineQubit.range(n_qubits) if isinstance(initial_state, list): initial_state = sum(1 << (n_qubits - 1 - i) for i in initial_state) # Initialize a random quadratic Hamiltonian quad_ham = random_quadratic_hamiltonian( n_qubits, conserves_particle_number, real=False) quad_ham_sparse = get_sparse_operator(quad_ham) # Compute the energy of the desired state if occupied_orbitals is None: energy = quad_ham.ground_energy() else: orbital_energies, _, constant = ( quad_ham.diagonalizing_bogoliubov_transform()) energy = sum(orbital_energies[i] for i in occupied_orbitals) + constant # Get the state using a circuit simulation circuit = cirq.Circuit.from_ops( prepare_gaussian_state( qubits, quad_ham, occupied_orbitals, initial_state=initial_state)) state = circuit.apply_unitary_effect_to_state(initial_state) # Check that the result is an eigenstate with the correct eigenvalue numpy.testing.assert_allclose( quad_ham_sparse.dot(state), energy * state, atol=atol)
def test_ground_state_particle_nonconserving(self): """Test getting the ground state preparation circuit for a Hamiltonian that does not conserve particle number.""" for n_qubits in self.n_qubits_range: # Initialize a particle-number-conserving Hamiltonian quadratic_hamiltonian = random_quadratic_hamiltonian( n_qubits, False, True) # Compute the true ground state sparse_operator = get_sparse_operator(quadratic_hamiltonian) ground_energy, _ = get_ground_state(sparse_operator) # Obtain the circuit circuit_description, start_orbitals = ( gaussian_state_preparation_circuit(quadratic_hamiltonian)) # Initialize the starting state state = jw_configuration_state(start_orbitals, n_qubits) # Apply the circuit particle_hole_transformation = ( jw_sparse_particle_hole_transformation_last_mode(n_qubits)) for parallel_ops in circuit_description: for op in parallel_ops: if op == 'pht': state = particle_hole_transformation.dot(state) else: i, j, theta, phi = op state = jw_sparse_givens_rotation( i, j, theta, phi, n_qubits).dot(state) # Check that the state obtained using the circuit is a ground state difference = sparse_operator * state - ground_energy * state discrepancy = numpy.amax(numpy.abs(difference)) self.assertAlmostEqual(discrepancy, 0)
def test_prepare_gaussian_state_with_spin_symmetry(n_spatial_orbitals, conserves_particle_number, occupied_orbitals, initial_state, atol=1e-5): n_qubits = 2 * n_spatial_orbitals qubits = LineQubit.range(n_qubits) # Initialize a random quadratic Hamiltonian quad_ham = random_quadratic_hamiltonian( n_spatial_orbitals, conserves_particle_number, real=True, expand_spin=True, seed=639) # Reorder the Hamiltonian and get sparse matrix quad_ham = openfermion.get_quadratic_hamiltonian( openfermion.reorder( openfermion.get_fermion_operator(quad_ham), openfermion.up_then_down) ) quad_ham_sparse = get_sparse_operator(quad_ham) # Compute the energy of the desired state energy = 0.0 for spin_sector in range(2): orbital_energies, _, _ = ( quad_ham.diagonalizing_bogoliubov_transform( spin_sector=spin_sector) ) energy += sum(orbital_energies[i] for i in occupied_orbitals[spin_sector]) energy += quad_ham.constant # Get the state using a circuit simulation circuit = cirq.Circuit.from_ops( prepare_gaussian_state( qubits, quad_ham, occupied_orbitals, initial_state=initial_state)) if isinstance(initial_state, list): initial_state = sum(1 << (n_qubits - 1 - i) for i in initial_state) state = circuit.apply_unitary_effect_to_state(initial_state) # Check that the result is an eigenstate with the correct eigenvalue numpy.testing.assert_allclose( quad_ham_sparse.dot(state), energy * state, atol=atol)
def test_non_particle_conserving(self): for n_qubits in self.n_qubits_range: # Initialize a particle-number-conserving Hamiltonian quadratic_hamiltonian = random_quadratic_hamiltonian( n_qubits, False) sparse_operator = get_sparse_operator(quadratic_hamiltonian) # Diagonalize the Hamiltonian using the circuit circuit_description = reversed( quadratic_hamiltonian.diagonalizing_circuit()) particle_hole_transformation = ( jw_sparse_particle_hole_transformation_last_mode(n_qubits)) for parallel_ops in circuit_description: for op in parallel_ops: if op == 'pht': gate = particle_hole_transformation else: i, j, theta, phi = op gate = jw_sparse_givens_rotation( i, j, theta, phi, n_qubits) sparse_operator = ( gate.getH().dot(sparse_operator).dot(gate)) # Check that the result is diagonal diag = scipy.sparse.diags(sparse_operator.diagonal()) difference = sparse_operator - diag discrepancy = 0. if difference.nnz: discrepancy = max(abs(difference.data)) self.assertTrue(discrepancy < EQ_TOLERANCE) # Check that the eigenvalues are in the expected order orbital_energies, constant = ( quadratic_hamiltonian.orbital_energies()) for index in range(2**n_qubits): bitstring = bin(index)[2:].zfill(n_qubits) subset = [j for j in range(n_qubits) if bitstring[j] == '1'] energy = sum([orbital_energies[j] for j in subset]) + constant self.assertAlmostEqual(sparse_operator[index, index], energy)
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])