def fermi_hubbard(x_dimension, y_dimension, tunneling, coulomb, chemical_potential=0., magnetic_field=0., periodic=True, spinless=False, particle_hole_symmetry=False): """Return symbolic representation of a Fermi-Hubbard Hamiltonian. The idea of this model is that some fermions move around on a grid and the energy of the model depends on where the fermions are. The Hamiltonians of this model live on a grid of dimensions `x_dimension` x `y_dimension`. The grid can have periodic boundary conditions or not. In the standard Fermi-Hubbard model (which we call the "spinful" model), there is room for an "up" fermion and a "down" fermion at each site on the grid. In this model, there are a total of `2N` spin-orbitals, where `N = x_dimension * y_dimension` is the number of sites. In the spinless model, there is only one spin-orbital per site for a total of `N`. The Hamiltonian for the spinful model has the form .. math:: \\begin{align} H = &- t \sum_{\langle i,j \\rangle} \sum_{\sigma} (a^\dagger_{i, \sigma} a_{j, \sigma} + a^\dagger_{j, \sigma} a_{i, \sigma}) + U \sum_{i} a^\dagger_{i, \\uparrow} a_{i, \\uparrow} a^\dagger_{j, \downarrow} a_{j, \downarrow} \\\\ &- \mu \sum_i (a^\dagger_{i, \\uparrow} a_{i, \\uparrow} + a^\dagger_{i, \downarrow} a_{i, \downarrow}) - h \sum_i (a^\dagger_{i, \\uparrow} a_{i, \\uparrow} - a^\dagger_{i, \downarrow} a_{i, \downarrow}) \\end{align} where - The indices :math:`\langle i, j \\rangle` run over pairs :math:`i` and :math:`j` of sites that are connected to each other in the grid - :math:`\sigma \in \\{\\uparrow, \downarrow\\}` is the spin - :math:`t` is the tunneling amplitude - :math:`U` is the Coulomb potential - :math:`\mu` is the chemical potential - :math:`h` is the magnetic field One can also construct the Hamiltonian for the spinless model, which has the form .. math:: H = - t \sum_{k=1}^{N-1} (a_k^\dagger a_{k + 1} + a_{k+1}^\dagger a_k) + U \sum_{k=1}^{N-1} a_k^\dagger a_k a_{k+1}^\dagger a_{k+1} + h \sum_{k=1}^N (-1)^k a_k^\dagger a_k - \mu \sum_{k=1}^N a_k^\dagger a_k. Args: x_dimension (int): The width of the grid. y_dimension (int): The height of the grid. tunneling (float): The tunneling amplitude :math:`t`. coulomb (float): The attractive local interaction strength :math:`U`. chemical_potential (float, optional): The chemical potential :math:`\mu` at each site. Default value is 0. magnetic_field (float, optional): The magnetic field :math:`h` at each site. Default value is 0. Ignored for the spinless case. periodic (bool, optional): If True, add periodic boundary conditions. Default is True. spinless (bool, optional): If True, return a spinless Fermi-Hubbard model. Default is False. particle_hole_symmetry (bool, optional): If False, the repulsion term corresponds to: .. math:: U \sum_{k=1}^{N-1} a_k^\dagger a_k a_{k+1}^\dagger a_{k+1} If True, the repulsion term is replaced by: .. math:: U \sum_{k=1}^{N-1} (a_k^\dagger a_k - \\frac12) (a_{k+1}^\dagger a_{k+1} - \\frac12) which is unchanged under a particle-hole transformation. Default is False Returns: hubbard_model: An instance of the FermionOperator class. """ tunneling = float(tunneling) coulomb = float(coulomb) chemical_potential = float(chemical_potential) magnetic_field = float(magnetic_field) # Initialize fermion operator class. n_sites = x_dimension * y_dimension n_spin_orbitals = n_sites if not spinless: n_spin_orbitals *= 2 hubbard_model = FermionOperator.zero() # Select particle-hole symmetry if particle_hole_symmetry: coulomb_shift = FermionOperator((), 0.5) else: coulomb_shift = FermionOperator.zero() # Loop through sites and add terms. for site in range(n_sites): # Add chemical potential to the spinless case. The magnetic field # doesn't contribute. if spinless and chemical_potential: hubbard_model += number_operator(n_spin_orbitals, site, -chemical_potential) # With spin, add the chemical potential and magnetic field terms. elif not spinless: hubbard_model += number_operator( n_spin_orbitals, up_index(site), -chemical_potential - magnetic_field) hubbard_model += number_operator( n_spin_orbitals, down_index(site), -chemical_potential + magnetic_field) # Add local pair interaction terms. operator_1 = number_operator(n_spin_orbitals, up_index(site)) - coulomb_shift operator_2 = number_operator(n_spin_orbitals, down_index(site)) - coulomb_shift hubbard_model += coulomb * operator_1 * operator_2 # Index coupled orbitals. right_neighbor = site + 1 bottom_neighbor = site + x_dimension # Account for periodic boundaries. if periodic: if (x_dimension > 2) and ((site + 1) % x_dimension == 0): right_neighbor -= x_dimension if (y_dimension > 2) and (site + x_dimension + 1 > n_sites): bottom_neighbor -= x_dimension * y_dimension # Add transition to neighbor on right. if (site + 1) % x_dimension or (periodic and x_dimension > 2): if spinless: # Add Coulomb term. operator_1 = number_operator(n_spin_orbitals, site, 1.0) - coulomb_shift operator_2 = number_operator(n_spin_orbitals, right_neighbor, 1.0) - coulomb_shift hubbard_model += coulomb * operator_1 * operator_2 # Add hopping term. operators = ((site, 1), (right_neighbor, 0)) else: # Add hopping term. operators = ((up_index(site), 1), (up_index(right_neighbor), 0)) hopping_term = FermionOperator(operators, -tunneling) hubbard_model += hopping_term hubbard_model += hermitian_conjugated(hopping_term) operators = ((down_index(site), 1), (down_index(right_neighbor), 0)) hopping_term = FermionOperator(operators, -tunneling) hubbard_model += hopping_term hubbard_model += hermitian_conjugated(hopping_term) # Add transition to neighbor below. if site + x_dimension + 1 <= n_sites or (periodic and y_dimension > 2): if spinless: # Add Coulomb term. operator_1 = number_operator(n_spin_orbitals, site) - coulomb_shift operator_2 = number_operator(n_spin_orbitals, bottom_neighbor) - coulomb_shift hubbard_model += coulomb * operator_1 * operator_2 # Add hopping term. operators = ((site, 1), (bottom_neighbor, 0)) else: # Add hopping term. operators = ((up_index(site), 1), (up_index(bottom_neighbor), 0)) hopping_term = FermionOperator(operators, -tunneling) hubbard_model += hopping_term hubbard_model += hermitian_conjugated(hopping_term) operators = ((down_index(site), 1), (down_index(bottom_neighbor), 0)) hopping_term = FermionOperator(operators, -tunneling) hubbard_model += hopping_term hubbard_model += hermitian_conjugated(hopping_term) return hubbard_model
def test_trivially_commutes_both_single_number_operators(self): self.assertTrue( trivially_commutes_dual_basis(FermionOperator('3^ 3'), FermionOperator('3^ 3')))
def test_trivially_commutes_one_double_number_operators(self): self.assertTrue( trivially_commutes_dual_basis(FermionOperator('3^ 2^ 3 2'), FermionOperator('3^ 3')))
def test_double_commutator_no_intersection_with_union_of_second_two(self): com = double_commutator(FermionOperator('4^ 3^ 6 5'), FermionOperator('2^ 1 0'), FermionOperator('0^')) self.assertTrue(com.isclose(FermionOperator.zero()))
def test_trivially_commutes_no_intersection(self): self.assertTrue( trivially_commutes_dual_basis(FermionOperator('3^ 2^ 3 2'), FermionOperator('4^ 1')))
def test_none_term(self): majorana_operator() self.assertEqual(majorana_operator(), FermionOperator())
def test_s_minus_operator(self): op = s_minus_operator(3) expected = (FermionOperator(((1, 1), (0, 0))) + FermionOperator(((3, 1), (2, 0))) + FermionOperator(((5, 1), (4, 0)))) self.assertEqual(op, expected)
def test_jw_sparse_0create_2annihilate(self): expected = csc_matrix(([-1j, 1j], ([4, 6], [1, 3])), shape=(8, 8)) self.assertTrue( numpy.allclose( jordan_wigner_sparse(FermionOperator('0^ 2', -1j)).A, expected.A))
def test_1d5_with_spin_7particles(self): dimension = 1 grid_length = 5 n_spatial_orbitals = grid_length**dimension wigner_seitz_radius = 9.3 spinless = False n_qubits = n_spatial_orbitals if not spinless: n_qubits *= 2 n_particles_big = 7 length_scale = wigner_seitz_length_scale(wigner_seitz_radius, n_particles_big, dimension) self.grid3 = Grid(dimension, grid_length, length_scale) # Get the occupied orbitals of the plane-wave basis Hartree-Fock state. hamiltonian = jellium_model(self.grid3, spinless, plane_wave=True) hamiltonian = normal_ordered(hamiltonian) hamiltonian.compress() occupied_states = numpy.array( lowest_single_particle_energy_states(hamiltonian, n_particles_big)) self.hf_state_index3 = numpy.sum(2**occupied_states) self.hf_state3 = csc_matrix(([1.0], ([self.hf_state_index3], [0])), shape=(2**n_qubits, 1)) self.orbital_occupations3 = [ digit == '1' for digit in bin(self.hf_state_index3)[2:] ][::-1] self.occupied_orbitals3 = [ index for index, occupied in enumerate(self.orbital_occupations3) if occupied ] self.reversed_occupied_orbitals3 = list(self.occupied_orbitals3) for i in range(len(self.reversed_occupied_orbitals3)): self.reversed_occupied_orbitals3[i] = -1 + int( numpy.log2(self.hf_state3.shape[0]) ) - self.reversed_occupied_orbitals3[i] self.reversed_hf_state_index3 = sum( 2**index for index in self.reversed_occupied_orbitals3) operator = (FermionOperator('6^ 0^ 1^ 3 5 4', 2) + FermionOperator('7^ 2^ 4 1') + FermionOperator('3^ 3', 2.1) + FermionOperator('5^ 3^ 1 0', 7.3)) operator = normal_ordered(operator) transformed_operator = normal_ordered( fourier_transform(operator, self.grid3, spinless)) expected = 1.66 - 0.0615536707435j # Calculated with expected = expectation(get_sparse_operator( # transformed_operator), self.hf_state3) actual = expectation_db_operator_with_pw_basis_state( operator, self.reversed_occupied_orbitals3, n_spatial_orbitals, self.grid3, spinless) self.assertAlmostEqual(expected, actual)
def test_jw_sparse_1annihilate(self): expected = csc_matrix(([1, -1], ([0, 2], [1, 3])), shape=(4, 4)) self.assertTrue( numpy.allclose( jordan_wigner_sparse(FermionOperator('1')).A, expected.A))
def test_expectation_state_is_list_single_number_terms(self): operator = FermionOperator('3^ 3', 1.9) + FermionOperator('2^ 1') state = [1, 1, 1, 1] self.assertAlmostEqual( expectation_computational_basis_state(operator, state), 1.9)
def test_expectation_fermion_operator_single_number_terms(self): operator = FermionOperator('3^ 3', 1.9) + FermionOperator('2^ 1') state = csc_matrix(([1], ([15], [0])), shape=(16, 1)) self.assertAlmostEqual( expectation_computational_basis_state(operator, state), 1.9)
def test_jw_sparse_0create(self): expected = csc_matrix(([1], ([1], [0])), shape=(2, 2)) self.assertTrue( numpy.allclose( jordan_wigner_sparse(FermionOperator('0^')).A, expected.A))
def mean_field_dwave(x_dimension, y_dimension, tunneling, sc_gap, periodic=True): """Return symbolic representation of a BCS mean-field d-wave Hamiltonian. The Hamiltonians of this model live on a grid of dimensions `x_dimension` x `y_dimension`. The grid can have periodic boundary conditions or not. Each site on the grid can have an "up" fermion and a "down" fermion. Therefore, there are a total of `2N` spin-orbitals, where `N = x_dimension * y_dimension` is the number of sites. The Hamiltonian for this model has the form .. math:: H = - t \sum_{\langle i,j \\rangle} \sum_\sigma (a^\dagger_{i, \sigma} a_{j, \sigma} + a^\dagger_{j, \sigma} a_{i, \sigma}) - \sum_{\langle i,j \\rangle} \Delta_{ij} (a^\dagger_{i, \\uparrow} a^\dagger_{j, \downarrow} - a^\dagger_{i, \downarrow} a^\dagger_{j, \\uparrow} + a_{j, \downarrow} a_{i, \\uparrow} - a_{j, \\uparrow} a_{i, \downarrow}) where - The indices :math:`\langle i, j \\rangle` run over pairs :math:`i` and :math:`j` of sites that are connected to each other in the grid - :math:`\sigma \in \\{\\uparrow, \downarrow\\}` is the spin - :math:`t` is the tunneling amplitude - :math:`\Delta_{ij}` is equal to :math:`+\Delta/2` for horizontal edges and :math:`-\Delta/2` for vertical edges, where :math:`\Delta` is the superconducting gap. Args: x_dimension (int): The width of the grid. y_dimension (int): The height of the grid. tunneling (float): The tunneling amplitude :math:`t`. sc_gap (float): The superconducting gap :math:`\Delta` periodic (bool, optional): If True, add periodic boundary conditions. Default is True. Returns: mean_field_dwave_model: An instance of the FermionOperator class. """ # Initialize fermion operator class. n_sites = x_dimension * y_dimension n_spin_orbitals = 2 * n_sites mean_field_dwave_model = FermionOperator() # Loop through sites and add terms. for site in range(n_sites): # Index coupled orbitals. right_neighbor = site + 1 bottom_neighbor = site + x_dimension # Account for periodic boundaries. if periodic: if (x_dimension > 2) and ((site + 1) % x_dimension == 0): right_neighbor -= x_dimension if (y_dimension > 2) and (site + x_dimension + 1 > n_sites): bottom_neighbor -= x_dimension * y_dimension # Add transition to neighbor on right if (site + 1) % x_dimension or (periodic and x_dimension > 2): # Add spin-up hopping term. operators = ((up_index(site), 1), (up_index(right_neighbor), 0)) hopping_term = FermionOperator(operators, -tunneling) mean_field_dwave_model += hopping_term mean_field_dwave_model += hermitian_conjugated(hopping_term) # Add spin-down hopping term operators = ((down_index(site), 1), (down_index(right_neighbor), 0)) hopping_term = FermionOperator(operators, -tunneling) mean_field_dwave_model += hopping_term mean_field_dwave_model += hermitian_conjugated(hopping_term) # Add pairing term operators = ((up_index(site), 1), (down_index(right_neighbor), 1)) pairing_term = FermionOperator(operators, sc_gap / 2.) operators = ((down_index(site), 1), (up_index(right_neighbor), 1)) pairing_term += FermionOperator(operators, -sc_gap / 2.) mean_field_dwave_model -= pairing_term mean_field_dwave_model -= hermitian_conjugated(pairing_term) # Add transition to neighbor below. if site + x_dimension + 1 <= n_sites or (periodic and y_dimension > 2): # Add spin-up hopping term. operators = ((up_index(site), 1), (up_index(bottom_neighbor), 0)) hopping_term = FermionOperator(operators, -tunneling) mean_field_dwave_model += hopping_term mean_field_dwave_model += hermitian_conjugated(hopping_term) # Add spin-down hopping term operators = ((down_index(site), 1), (down_index(bottom_neighbor), 0)) hopping_term = FermionOperator(operators, -tunneling) mean_field_dwave_model += hopping_term mean_field_dwave_model += hermitian_conjugated(hopping_term) # Add pairing term operators = ((up_index(site), 1), (down_index(bottom_neighbor), 1)) pairing_term = FermionOperator(operators, -sc_gap / 2.) operators = ((down_index(site), 1), (up_index(bottom_neighbor), 1)) pairing_term += FermionOperator(operators, sc_gap / 2.) mean_field_dwave_model -= pairing_term mean_field_dwave_model -= hermitian_conjugated(pairing_term) # Return. return mean_field_dwave_model
def test_bk_n_qubits_too_small(self): with self.assertRaises(ValueError): bravyi_kitaev_tree(FermionOperator('2^ 3^ 5 0'), n_qubits=4)
def test_jw_sparse_twobody(self): expected = csc_matrix(([1, 1], ([6, 14], [5, 13])), shape=(16, 16)) self.assertTrue( numpy.allclose( jordan_wigner_sparse(FermionOperator('2^ 1^ 1 3')).A, expected.A))
def test_fermion_number_operator_site(self): op = number_operator(3, 2, 1j, -1) self.assertEqual(op, FermionOperator(((2, 1), (2, 0))) * 1j) op = number_operator(3, 2, 1j, 1) self.assertTrue(op == BosonOperator(((2, 1), (2, 0))) * 1j)
def test_get_quadratic_hamiltonian_too_few_qubits(self): """Test asking for too few qubits.""" with self.assertRaises(ValueError): get_quadratic_hamiltonian(FermionOperator('3^ 2^'), n_qubits=3)
def test_s_plus_operator(self): op = s_plus_operator(2) expected = (FermionOperator(((0, 1), (1, 0))) + FermionOperator(((2, 1), (3, 0)))) self.assertEqual(op, expected)
def test_get_interaction_operator_too_few_qubits(self): with self.assertRaises(ValueError): get_interaction_operator(FermionOperator('3^ 2^ 1 0'), 3)
def test_anticommutator_not_same_type(self): with self.assertRaises(TypeError): anticommutator(FermionOperator(), QubitOperator())
def test_get_interaction_operator_bad_2body_term(self): with self.assertRaises(InteractionOperatorError): get_interaction_operator(FermionOperator('3^ 2 1 0'))
def setUp(self): self.fermion_term = FermionOperator('1^ 2^ 3 4', -3.17) self.fermion_operator = self.fermion_term + hermitian_conjugated( self.fermion_term) self.qubit_operator = jordan_wigner(self.fermion_operator)
def test_get_interaction_operator_nonmolecular_term(self): with self.assertRaises(InteractionOperatorError): get_interaction_operator(FermionOperator('3^ 2 1'))
def test_no_trivial_commute_with_intersection(self): self.assertFalse( trivially_commutes_dual_basis(FermionOperator('2^ 1'), FermionOperator('5^ 2^ 5 2')))
def setUp(self): self.hermitian_op = FermionOperator((), 1.) self.hermitian_op += FermionOperator('1^ 1', 3.) self.hermitian_op += FermionOperator('1^ 2', 3. + 4.j) self.hermitian_op += FermionOperator('2^ 1', 3. - 4.j) self.hermitian_op += FermionOperator('3^ 4^', 2. + 5.j) self.hermitian_op += FermionOperator('4 3', 2. - 5.j) self.hermitian_op_pc = FermionOperator((), 1.) self.hermitian_op_pc += FermionOperator('1^ 1', 3.) self.hermitian_op_pc += FermionOperator('1^ 2', 3. + 4.j) self.hermitian_op_pc += FermionOperator('2^ 1', 3. - 4.j) self.hermitian_op_pc += FermionOperator('3^ 4', 2. + 5.j) self.hermitian_op_pc += FermionOperator('4^ 3', 2. - 5.j) self.hermitian_op_bad_term = FermionOperator('1^ 1 2', 3.) self.hermitian_op_bad_term += FermionOperator('2^ 1^ 1', 3.) self.not_hermitian_1 = FermionOperator('2^ 0^') self.not_hermitian_2 = FermionOperator('3^ 0^') self.not_hermitian_2 += FermionOperator('3 0', 3.) self.not_hermitian_3 = FermionOperator('2 0') self.not_hermitian_4 = FermionOperator('4 0') self.not_hermitian_4 += FermionOperator('4^ 0^', 3.) self.not_hermitian_5 = FermionOperator('2^ 3', 3.) self.not_hermitian_5 += FermionOperator('3^ 2', 2.)
def test_trivially_commutes_nonintersecting_single_number_operators(self): self.assertTrue( trivially_commutes_dual_basis(FermionOperator('2^ 2'), FermionOperator('3^ 3')))
def test_bk_identity(self): self.assertTrue(bravyi_kitaev_tree(FermionOperator(())) == QubitOperator(()))
def test_commutes_identity(self): com = commutator(FermionOperator.identity(), FermionOperator('2^ 3', 2.3)) self.assertTrue(com.isclose(FermionOperator.zero()))
def test_bad_input(self): with self.assertRaises(TypeError): _bksf.bravyi_kitaev_fast(FermionOperator())