def test_diagonalizing_bogoliubov_transform_particle_conserving(self): """Test particle-conserving diagonalizing Bogoliubov transform.""" # Spin-symmetric quad_ham = random_quadratic_hamiltonian(5, conserves_particle_number=True, expand_spin=True) quad_ham = get_quadratic_hamiltonian( reorder(get_fermion_operator(quad_ham), up_then_down)) orbital_energies, transformation_matrix, _ = ( quad_ham.diagonalizing_bogoliubov_transform()) max_upper_right = numpy.max(numpy.abs(transformation_matrix[:5, 5:])) max_lower_left = numpy.max(numpy.abs(transformation_matrix[5:, :5])) numpy.testing.assert_allclose(orbital_energies[:5], orbital_energies[5:]) numpy.testing.assert_allclose(transformation_matrix.dot( quad_ham.combined_hermitian_part.T.dot( transformation_matrix.T.conj())), numpy.diag(orbital_energies), atol=1e-7) numpy.testing.assert_allclose(max_upper_right, 0.0) numpy.testing.assert_allclose(max_lower_left, 0.0) # Specific spin sector quad_ham = random_quadratic_hamiltonian(5, conserves_particle_number=True, expand_spin=True) quad_ham = get_quadratic_hamiltonian( reorder(get_fermion_operator(quad_ham), up_then_down)) for spin_sector in range(2): orbital_energies, transformation_matrix, _ = ( quad_ham.diagonalizing_bogoliubov_transform( spin_sector=spin_sector)) def index_map(i): return i + spin_sector * 5 spin_indices = [index_map(i) for i in range(5)] spin_matrix = quad_ham.combined_hermitian_part[numpy.ix_( spin_indices, spin_indices)] numpy.testing.assert_allclose(transformation_matrix.dot( spin_matrix.T.dot(transformation_matrix.T.conj())), numpy.diag(orbital_energies), atol=1e-7) # Not spin-symmetric quad_ham = random_quadratic_hamiltonian(5, conserves_particle_number=True, expand_spin=False) orbital_energies, _ = quad_ham.orbital_energies() _, transformation_matrix, _ = ( quad_ham.diagonalizing_bogoliubov_transform()) numpy.testing.assert_allclose(transformation_matrix.dot( quad_ham.combined_hermitian_part.T.dot( transformation_matrix.T.conj())), numpy.diag(orbital_energies), atol=1e-7)
def symmetry_conserving_bravyi_kitaev(fermion_hamiltonian, active_orbitals, active_fermions): """ Returns the qubit Hamiltonian for the fermionic Hamiltonian supplied, with two qubits removed using conservation of electron spin and number, as described in arXiv:1701.08213. Args: fermion_hamiltonian: A fermionic hamiltonian obtained using OpenFermion. An instance of the FermionOperator class. active_orbitals: Int type object. The number of active orbitals being considered for the system. active_fermions: Int type object. The number of active fermions being considered for the system (note, this is less than the number of electrons in a molecule if some orbitals have been assumed filled). Returns: qubit_hamiltonian: The qubit Hamiltonian corresponding to the supplied fermionic Hamiltonian, with two qubits removed using spin symmetries. WARNING: Reorders orbitals from the default even-odd ordering to all spin-up orbitals, then all spin-down orbitals. Raises: ValueError if fermion_hamiltonian isn't of the type FermionOperator, or active_orbitals isn't an integer, or active_fermions isn't an integer. Notes: This function reorders the spin orbitals as all spin-up, then all spin-down. It uses the OpenFermion bravyi_kitaev_tree mapping, rather than the bravyi-kitaev mapping. Caution advised when using with a Fermi-Hubbard Hamiltonian; this technique correctly reduces the Hamiltonian only for the lowest energy even and odd fermion number states, not states with an arbitrary number of fermions. """ # Catch errors if inputs are of wrong type. if type(fermion_hamiltonian) is not FermionOperator: raise ValueError( "Supplied operator should be an instance of FermionOperator class") if type(active_orbitals) is not int: raise ValueError("Number of active orbitals should be an integer.") if type(active_fermions) is not int: raise ValueError("Number of active fermions should be an integer.") # Arrange spins up then down, then BK map to qubit Hamiltonian. fermion_hamiltonian_reorder = reorder(fermion_hamiltonian, up_then_down, num_modes=active_orbitals) qubit_hamiltonian = bravyi_kitaev_tree(fermion_hamiltonian_reorder, n_qubits=active_orbitals) qubit_hamiltonian.compress() # Allocates the parity factors for the orbitals as in arXiv:1704.05018. remainder = active_fermions % 4 if remainder == 0: parity_final_orb = 1 parity_middle_orb = 1 elif remainder == 1: parity_final_orb = -1 parity_middle_orb = -1 elif remainder == 2: parity_final_orb = 1 parity_middle_orb = -1 else: parity_final_orb = -1 parity_middle_orb = 1 # Removes the final qubit, then the middle qubit. qubit_hamiltonian = edit_hamiltonian_for_spin(qubit_hamiltonian, active_orbitals, parity_final_orb) qubit_hamiltonian = edit_hamiltonian_for_spin(qubit_hamiltonian, active_orbitals / 2, parity_middle_orb) qubit_hamiltonian = remove_indices(qubit_hamiltonian, (active_orbitals / 2, active_orbitals)) return qubit_hamiltonian