def _spinful_fermi_hubbard_model(x_dimension, y_dimension, tunneling, coulomb, chemical_potential, magnetic_field, periodic, particle_hole_symmetry): # Initialize operator. n_sites = x_dimension * y_dimension n_spin_orbitals = 2 * n_sites hubbard_model = FermionOperator() # Loop through sites and add terms. for site in range(n_sites): # Get indices of right and bottom neighbors right_neighbor = _right_neighbor(site, x_dimension, y_dimension, periodic) bottom_neighbor = _bottom_neighbor(site, x_dimension, y_dimension, periodic) # Avoid double-counting edges when one of the dimensions is 2 # and the system is periodic if x_dimension == 2 and periodic and site % 2 == 1: right_neighbor = None if y_dimension == 2 and periodic and site >= x_dimension: bottom_neighbor = None # Add hopping terms with neighbors to the right and bottom. if right_neighbor is not None: hubbard_model += _hopping_term(up_index(site), up_index(right_neighbor), -tunneling) hubbard_model += _hopping_term(down_index(site), down_index(right_neighbor), -tunneling) if bottom_neighbor is not None: hubbard_model += _hopping_term(up_index(site), up_index(bottom_neighbor), -tunneling) hubbard_model += _hopping_term(down_index(site), down_index(bottom_neighbor), -tunneling) # Add local pair Coulomb interaction terms. hubbard_model += _coulomb_interaction_term(n_spin_orbitals, up_index(site), down_index(site), coulomb, particle_hole_symmetry) # Add chemical potential and magnetic field terms. 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) return hubbard_model
def _coulomb_interaction_term(n_sites, i, j, coefficient, particle_hole_symmetry, bosonic=False): op_class = BosonOperator if bosonic else FermionOperator number_operator_i = number_operator(n_sites, i, parity=2 * bosonic - 1) number_operator_j = number_operator(n_sites, j, parity=2 * bosonic - 1) if particle_hole_symmetry: number_operator_i -= op_class((), 0.5) number_operator_j -= op_class((), 0.5) return coefficient * number_operator_i * number_operator_j
def test_jw_get_ground_states_by_particle_number_herm_conserving(self): # Initialize a particle-number-conserving Hermitian operator ferm_op = FermionOperator('0^ 1') + FermionOperator('1^ 0') + \ FermionOperator('1^ 2') + FermionOperator('2^ 1') + \ FermionOperator('1^ 3', -.4) + FermionOperator('3^ 1', -.4) jw_hamiltonian = jordan_wigner(ferm_op) sparse_operator = get_sparse_operator(jw_hamiltonian) n_qubits = 4 # Test each possible particle number for particle_number in range(n_qubits): # Get the ground energy and ground states at this particle number energy, states = jw_get_ground_states_by_particle_number( sparse_operator, particle_number) # Construct particle number operator num_op = get_sparse_operator(number_operator(n_qubits)) # For each vector returned, make sure that it is indeed an # eigenvector of the original operator with the returned eigenvalue # and that it has the correct particle number for vec in states: # Check that it's an eigenvector with the correct eigenvalue op_vec_product = sparse_operator.dot(vec) difference = op_vec_product - energy * vec discrepancy = 0. if difference.nnz: discrepancy = max(map(abs, difference.data)) self.assertAlmostEqual(0., discrepancy) # Check that it has the correct particle number num = expectation(num_op, vec) self.assertAlmostEqual(num, particle_number)
def test_jordan_wigner_transm_op(self): n = number_operator(self.n_qubits) n_jw = jordan_wigner(n) self.assertEqual(self.n_qubits + 1, len(n_jw.terms)) self.assertEqual(self.n_qubits / 2., n_jw.terms[()]) for qubit in range(self.n_qubits): operators = ((qubit, 'Z'), ) self.assertEqual(n_jw.terms[operators], -0.5)
def test_bk_jw_number_operators(self): # Check if a number operator has the same spectrum in both # JW and BK representations n_qubits = 2 n1 = number_operator(n_qubits, 0) n2 = number_operator(n_qubits, 1) n = n1 + n2 jw_n = jordan_wigner(n) bk_n = bravyi_kitaev(n) # Diagonalize and make sure the spectra are the same. jw_spectrum = eigenspectrum(jw_n) bk_spectrum = eigenspectrum(bk_n) self.assertAlmostEqual(0., numpy.amax( numpy.absolute(jw_spectrum - bk_spectrum)))
def test_bk_jw_number_operator(self): # Check if number operator has the same spectrum in both # BK and JW representations n = number_operator(1, 0) jw_n = jordan_wigner(n) bk_n = bravyi_kitaev(n) # Diagonalize and make sure the spectra are the same. jw_spectrum = eigenspectrum(jw_n) bk_spectrum = eigenspectrum(bk_n) self.assertAlmostEqual(0., numpy.amax( numpy.absolute(jw_spectrum - bk_spectrum)))
def test_bk_jw_number_operator_scaled(self): # Check if number operator has the same spectrum in both # JW and BK representations n_qubits = 1 n = number_operator(n_qubits, 0, coefficient=2) # eigenspectrum (0,2) jw_n = jordan_wigner(n) bk_n = bravyi_kitaev(n) # Diagonalize and make sure the spectra are the same. jw_spectrum = eigenspectrum(jw_n) bk_spectrum = eigenspectrum(bk_n) self.assertAlmostEqual(0., numpy.amax( numpy.absolute(jw_spectrum - bk_spectrum)))
def test_jw_restrict_jellium_ground_state_integration(self): n_qubits = 4 grid = Grid(dimensions=1, length=n_qubits, scale=1.0) jellium_hamiltonian = jordan_wigner_sparse( jellium_model(grid, spinless=False)) # 2 * n_qubits because of spin number_sparse = jordan_wigner_sparse(number_operator(2 * n_qubits)) restricted_number = jw_number_restrict_operator(number_sparse, 2) restricted_jellium_hamiltonian = jw_number_restrict_operator( jellium_hamiltonian, 2) energy, ground_state = get_ground_state(restricted_jellium_hamiltonian) number_expectation = expectation(restricted_number, ground_state) self.assertAlmostEqual(number_expectation, 2)
def _spinless_fermi_hubbard_model(x_dimension, y_dimension, tunneling, coulomb, chemical_potential, magnetic_field, periodic, particle_hole_symmetry): # Initialize operator. n_sites = x_dimension * y_dimension hubbard_model = FermionOperator() # Loop through sites and add terms. for site in range(n_sites): # Get indices of right and bottom neighbors right_neighbor = _right_neighbor(site, x_dimension, y_dimension, periodic) bottom_neighbor = _bottom_neighbor(site, x_dimension, y_dimension, periodic) # Avoid double-counting edges when one of the dimensions is 2 # and the system is periodic if x_dimension == 2 and periodic and site % 2 == 1: right_neighbor = None if y_dimension == 2 and periodic and site >= x_dimension: bottom_neighbor = None # Add terms that couple with neighbors to the right and bottom. if right_neighbor is not None: # Add hopping term hubbard_model += _hopping_term(site, right_neighbor, -tunneling) # Add local Coulomb interaction term hubbard_model += _coulomb_interaction_term(n_sites, site, right_neighbor, coulomb, particle_hole_symmetry) if bottom_neighbor is not None: # Add hopping term hubbard_model += _hopping_term(site, bottom_neighbor, -tunneling) # Add local Coulomb interaction term hubbard_model += _coulomb_interaction_term(n_sites, site, bottom_neighbor, coulomb, particle_hole_symmetry) # Add chemical potential. The magnetic field doesn't contribute. hubbard_model += number_operator(n_sites, site, -chemical_potential) return hubbard_model
def test_jw_restrict_operator(self): """Test the scheme for restricting JW encoded operators to number""" # Make a Hamiltonian that cares mostly about number of electrons n_qubits = 6 target_electrons = 3 penalty_const = 100. number_sparse = jordan_wigner_sparse(number_operator(n_qubits)) bias_sparse = jordan_wigner_sparse( sum([FermionOperator(((i, 1), (i, 0)), 1.0) for i in range(n_qubits)], FermionOperator())) hamiltonian_sparse = penalty_const * ( number_sparse - target_electrons * scipy.sparse.identity(2**n_qubits)).dot( number_sparse - target_electrons * scipy.sparse.identity(2**n_qubits)) + bias_sparse restricted_hamiltonian = jw_number_restrict_operator( hamiltonian_sparse, target_electrons, n_qubits) true_eigvals, _ = eigh(hamiltonian_sparse.A) test_eigvals, _ = eigh(restricted_hamiltonian.A) self.assertAlmostEqual(norm(true_eigvals[:20] - test_eigvals[:20]), 0.0)
def test_boson_number_reversed(self): n_term_rev2 = BosonOperator(((2, 0), (2, 1))) number_op2 = number_operator(3, 2, parity=1) expected = BosonOperator(()) + number_op2 self.assertTrue(normal_ordered(n_term_rev2) == expected)
def test_is_two_body_number_conserving_number(self): op = number_operator(5, 3) self.assertTrue(op.is_two_body_number_conserving())
def jw_get_ground_states_by_particle_number(sparse_operator, particle_number, sparse=True, num_eigs=3): """For a Jordan-Wigner encoded Hermitian operator, compute the lowest eigenvalue and eigenstates at a particular particle number. The operator must conserve particle number. Args: sparse_operator(sparse): A Jordan-Wigner encoded sparse operator. particle_number(int): The particle number at which to compute ground states. sparse(boolean, optional): Whether to use sparse eigensolver. Default is True. num_eigs(int, optional): The number of eigenvalues to request from the sparse eigensolver. Needs to be at least as large as the degeneracy of the ground energy in order to obtain all ground states. Only used if `sparse=True`. Default is 3. Returns: ground_energy(float): The lowest eigenvalue of sparse_operator within the eigenspace of the number operator corresponding to particle_number. ground_states(list[ndarray]): A list of the corresponding eigenstates. Warning: The running time of this method is exponential in the number of qubits. """ # Check if operator is Hermitian if not is_hermitian(sparse_operator): raise ValueError('sparse_operator must be Hermitian.') n_qubits = int(numpy.log2(sparse_operator.shape[0])) # Check if operator conserves particle number sparse_num_op = jordan_wigner_sparse(number_operator(n_qubits)) com = commutator(sparse_num_op, sparse_operator) if com.nnz: maxval = max(map(abs, com.data)) if maxval > EQ_TOLERANCE: raise ValueError('sparse_operator must conserve particle number.') # Get the operator restricted to the subspace of the desired # particle number restricted_operator = jw_number_restrict_operator(sparse_operator, particle_number, n_qubits) if sparse and num_eigs >= restricted_operator.shape[0] - 1: # Restricted operator too small for sparse eigensolver sparse = False # Compute eigenvalues and eigenvectors if sparse: eigvals, eigvecs = scipy.sparse.linalg.eigsh(restricted_operator, k=num_eigs, which='SA') if abs(max(eigvals) - min(eigvals)) < EQ_TOLERANCE: warnings.warn( 'The lowest {} eigenvalues are degenerate. ' 'There may be more ground states; increase ' 'num_eigs or set sparse=False to get ' 'them.'.format(num_eigs), RuntimeWarning) else: dense_restricted_operator = restricted_operator.toarray() eigvals, eigvecs = numpy.linalg.eigh(dense_restricted_operator) # Get the ground energy if sparse: ground_energy = sorted(eigvals)[0] else: # No need to sort in the case of dense eigenvalue computation ground_energy = eigvals[0] # Get the indices of eigenvectors corresponding to the ground energy ground_state_indices = numpy.where( abs(eigvals - ground_energy) < EQ_TOLERANCE) ground_states = list() for i in ground_state_indices[0]: restricted_ground_state = eigvecs[:, i] # Expand this ground state to the whole vector space number_indices = jw_number_indices(particle_number, n_qubits) expanded_ground_state = scipy.sparse.csc_matrix( (restricted_ground_state.flatten(), (number_indices, [0] * len(number_indices))), shape=(2**n_qubits, 1)) # Add the expanded ground state to the list ground_states.append(expanded_ground_state) return ground_energy, ground_states
def reverse_jordan_wigner(qubit_operator, n_qubits=None): """Transforms a QubitOperator into a FermionOperator using the Jordan-Wigner transform. Operators are mapped as follows: Z_j -> I - 2 a^\dagger_j a_j X_j -> (a^\dagger_j + a_j) Z_{j-1} Z_{j-2} .. Z_0 Y_j -> i (a^\dagger_j - a_j) Z_{j-1} Z_{j-2} .. Z_0 Args: qubit_operator: the QubitOperator to be transformed. n_qubits: the number of qubits term acts on. If not set, defaults to the maximum qubit number acted on by term. Returns: transformed_term: An instance of the FermionOperator class. Raises: TypeError: Input must be a QubitOperator. TypeError: Invalid number of qubits specified. TypeError: Pauli operators must be X, Y or Z. """ if not isinstance(qubit_operator, QubitOperator): raise TypeError('Input must be a QubitOperator.') if n_qubits is None: n_qubits = count_qubits(qubit_operator) if n_qubits < count_qubits(qubit_operator): raise ValueError('Invalid number of qubits specified.') # Loop through terms. transformed_operator = FermionOperator() for term in qubit_operator.terms: transformed_term = FermionOperator(()) if term: working_term = QubitOperator(term) pauli_operator = term[-1] while pauli_operator is not None: # Handle Pauli Z. if pauli_operator[1] == 'Z': transformed_pauli = FermionOperator( ()) + number_operator(n_qubits, pauli_operator[0], -2.) # Handle Pauli X and Y. else: raising_term = FermionOperator(((pauli_operator[0], 1), )) lowering_term = FermionOperator(((pauli_operator[0], 0), )) if pauli_operator[1] == 'Y': raising_term *= 1.j lowering_term *= -1.j transformed_pauli = raising_term + lowering_term # Account for the phase terms. for j in reversed(range(pauli_operator[0])): z_term = QubitOperator(((j, 'Z'), )) working_term = z_term * working_term term_key = list(working_term.terms)[0] transformed_pauli *= working_term.terms[term_key] working_term.terms[list(working_term.terms)[0]] = 1. # Get next non-identity operator acting below 'working_qubit'. assert len(working_term.terms) == 1 working_qubit = pauli_operator[0] - 1 for working_operator in reversed(list(working_term.terms)[0]): if working_operator[0] <= working_qubit: pauli_operator = working_operator break else: pauli_operator = None # Multiply term by transformed operator. transformed_term *= transformed_pauli # Account for overall coefficient transformed_term *= qubit_operator.terms[term] transformed_operator += transformed_term return transformed_operator
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 \sum_{\sigma} a^\dagger_{i, \sigma} a_{i, \sigma} - 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} - \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 (right_neighbor) % 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_is_molecular_term_number(self): op = number_operator(5, 3) self.assertTrue(op.is_molecular_term())
def test_number_operator_nosite(self): op = number_operator(4) expected = (FermionOperator(((0, 1), (0, 0))) + FermionOperator( ((1, 1), (1, 0))) + FermionOperator( ((2, 1), (2, 0))) + FermionOperator(((3, 1), (3, 0)))) self.assertTrue(op.isclose(expected))
def get_operators(self, guess="minao", pyscf_mol=None): if mpi.main_rank: # Run electronic structure calculations if pyscf_mol is None: self, pyscf_mol = run_pyscf_mod(guess, self.n_orbitals, self.n_electrons, self, run_casci=self.run_fci) # 'n_electrons' and 'n_orbitals' must not be 'None'. n_core_orbitals = (self.n_electrons - self.n_electrons) // 2 occupied_indices = list(range(n_core_orbitals)) active_indices = list( range(n_core_orbitals, n_core_orbitals + self.n_orbitals)) hf_energy = self.hf_energy fci_energy = self.fci_energy mo_coeff = self.canonical_orbitals.astype(float) natom = pyscf_mol.natm #atom_charges = pyscf_mol.atom_charges().reshape(-1, 1) atom_charges = pyscf_mol.atom_charges().reshape(1, -1) atom_coords = pyscf_mol.atom_coords() rint = pyscf_mol.intor("int1e_r") Hamiltonian = self.get_molecular_hamiltonian( occupied_indices=occupied_indices, active_indices=active_indices) # Dipole operators from dipole integrals (AO) rx = create_1body_operator(mo_coeff, rint[0], ao=True, n_active_orbitals=self.n_orbitals) ry = create_1body_operator(mo_coeff, rint[1], ao=True, n_active_orbitals=self.n_orbitals) rz = create_1body_operator(mo_coeff, rint[2], ao=True, n_active_orbitals=self.n_orbitals) Dipole = np.array([rx, ry, rz]) else: Hamiltonian = None Dipole = None hf_energy = None fci_energy = None mo_coeff = None natom = None atom_charges = None atom_coords = None # MPI broadcasting Hamiltonian = mpi.comm.bcast(Hamiltonian, root=0) Dipole = mpi.comm.bcast(Dipole, root=0) hf_energy = mpi.comm.bcast(hf_energy, root=0) fci_energy = mpi.comm.bcast(fci_energy, root=0) mo_coeff = mpi.comm.bcast(mo_coeff, root=0) natom = mpi.comm.bcast(natom, root=0) atom_charges = mpi.comm.bcast(atom_charges, root=0) atom_coords = mpi.comm.bcast(atom_coords, root=0) # Put values in self self.hf_energy = hf_energy self.fci_energy = fci_energy self.mo_coeff = mo_coeff self.natom = natom self.atom_charges = atom_charges self.atom_coords = atom_coords # Print out some results print_geom(self.geometry) prints("E[FCI] = ", fci_energy) prints("E[HF] = ", hf_energy) prints("") S2 = s_squared_operator(self.n_orbitals) Number = number_operator(self.n_orbitals) return Hamiltonian, S2, Number, Dipole
def test_is_boson_preserving_number(self): op = number_operator(n_modes=5, mode=3, parity=1) self.assertTrue(op.is_boson_preserving())
def _hubbard(parity, x_dimension, y_dimension, tunneling, local_interaction, coulomb, chemical_potential=0., magnetic_field=0., periodic=True, spinless=False, particle_hole_symmetry=False): r"""Returns a generic Hubbard-style Hamiltonian, to be used by the bose_hubbard and fermi_hubbard wrapper functions. See the corresponding bose_hubbard and fermi_hubbard for descriptions of the various arguments. Args: parity (int): parity=-1 returns the output as an instance of the FermionOperator class. Alternatively, parity=1 returns the output as an instance of the BosonOperator class Returns: hubbard_model: An instance of the FermionOperator or BosonOperator class. """ tunneling = float(tunneling) local_interaction = float(local_interaction) coulomb = float(coulomb) chemical_potential = float(chemical_potential) magnetic_field = float(magnetic_field) if parity == -1: Op = FermionOperator elif parity == 1: Op = BosonOperator # Initialize operator class. n_sites = x_dimension * y_dimension n_spin_orbitals = n_sites if not spinless: n_spin_orbitals *= 2 hubbard_model = Op.zero() # Select particle-hole symmetry if particle_hole_symmetry: coulomb_shift = Op((), 0.5) else: coulomb_shift = Op.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, parity=parity) # no Pauli-Exclusion principle, spinless particles interact on-site if parity == 1: int_op = number_operator( n_sites, site, local_interaction, parity=1) \ * (number_operator(n_sites, site, parity=1) - Op.identity()) hubbard_model += int_op # 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, parity=parity) hubbard_model += number_operator(n_spin_orbitals, down_index(site), -chemical_potential + magnetic_field, parity=parity) # Add local pair interaction terms. operator_1 = number_operator( n_spin_orbitals, up_index(site), parity=parity) \ - coulomb_shift operator_2 = number_operator( n_spin_orbitals, down_index(site), parity=parity) \ - 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 (right_neighbor) % x_dimension or (periodic and x_dimension > 2): if spinless: # Add Coulomb term. operator_1 = number_operator( n_spin_orbitals, site, 1.0, parity=parity) - coulomb_shift operator_2 = number_operator( n_spin_orbitals, right_neighbor, 1.0, parity=parity) \ - 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 = Op(operators, -tunneling) hubbard_model += hopping_term hubbard_model += hermitian_conjugated(hopping_term) operators = ((down_index(site), 1), (down_index(right_neighbor), 0)) hopping_term = Op(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, parity=parity) - coulomb_shift operator_2 = number_operator( n_spin_orbitals, bottom_neighbor, parity=parity) \ - 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 = Op(operators, -tunneling) hubbard_model += hopping_term hubbard_model += hermitian_conjugated(hopping_term) operators = ((down_index(site), 1), (down_index(bottom_neighbor), 0)) hopping_term = Op(operators, -tunneling) hubbard_model += hopping_term hubbard_model += hermitian_conjugated(hopping_term) return hubbard_model
def test_transm_number(self): n = number_operator(self.n_qubits, 3) n_jw = jordan_wigner(n) self.assertEqual(n_jw.terms[((3, 'Z'), )], -0.5) self.assertEqual(n_jw.terms[()], 0.5) self.assertEqual(len(n_jw.terms), 2)
def test_fermion_number_reversed(self): n_term_rev2 = FermionOperator(((2, 0), (2, 1))) number_op2 = number_operator(3, 2) expected = FermionOperator(()) - number_op2 self.assertTrue(normal_ordered(n_term_rev2) == expected)
def test_is_molecular_term_number(self): op = number_operator(n_orbitals=5, orbital=3) self.assertTrue(op.is_molecular_term())
def mean_field_dwave(x_dimension, y_dimension, tunneling, sc_gap, chemical_potential=0., periodic=True, up_map=up_index, down_map=down_index): """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:: \\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}) - \mu \sum_i \sum_{\sigma} a^\dagger_{i, \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}) \\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:`\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. - :math:`\mu` is the chemical potential 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` chemical_potential (float, optional): The chemical potential :math:`\mu` at each site. Default value is 0. 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): # Add chemical potential mean_field_dwave_model += number_operator(n_spin_orbitals, up_map(site), -chemical_potential) mean_field_dwave_model += number_operator(n_spin_orbitals, down_map(site), -chemical_potential) # 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_map(site), 1), (up_map(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_map(site), 1), (down_map(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_map(site), 1), (down_map(right_neighbor), 1)) pairing_term = FermionOperator(operators, sc_gap / 2.) operators = ((down_map(site), 1), (up_map(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_map(site), 1), (up_map(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_map(site), 1), (down_map(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_map(site), 1), (down_map(bottom_neighbor), 1)) pairing_term = FermionOperator(operators, -sc_gap / 2.) operators = ((down_map(site), 1), (up_map(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 bose_hubbard(x_dimension, y_dimension, tunneling, interaction, chemical_potential=0., dipole=0., periodic=True): r"""Return symbolic representation of a Bose-Hubbard Hamiltonian. In this model, bosons move around on a lattice, and the energy of the model depends on where the bosons are. The lattice is described by a 2D grid, with dimensions `x_dimension` x `y_dimension`. It is also possible to specify if the grid has periodic boundary conditions or not. The Hamiltonian for the Bose-Hubbard model has the form .. math:: H = - t \sum_{\langle i, j \rangle} (b_i^\dagger b_j + b_j^\dagger b_i) + V \sum_{\langle i, j \rangle} b_i^\dagger b_i b_j^\dagger b_j + \frac{U}{2} \sum_i b_i^\dagger b_i (b_i^\dagger b_i - 1) - \mu \sum_i b_i^\dagger b_i. where - The indices :math:`\langle i, j \rangle` run over pairs :math:`i` and :math:`j` of nodes that are connected to each other in the grid - :math:`t` is the tunneling amplitude - :math:`U` is the on-site interaction potential - :math:`\mu` is the chemical potential - :math:`V` is the dipole or nearest-neighbour interaction potential Args: x_dimension (int): The width of the grid. y_dimension (int): The height of the grid. tunneling (float): The tunneling amplitude :math:`t`. interaction (float): The attractive local interaction strength :math:`U`. chemical_potential (float, optional): The chemical potential :math:`\mu` at each site. Default value is 0. periodic (bool, optional): If True, add periodic boundary conditions. Default is True. dipole (float): The attractive dipole interaction strength :math:`V`. Returns: bose_hubbard_model: An instance of the BosonOperator class. """ # Initialize operator. n_sites = x_dimension * y_dimension hubbard_model = BosonOperator() # Loop through sites and add terms. for site in range(n_sites): # Get indices of right and bottom neighbors right_neighbor = _right_neighbor(site, x_dimension, y_dimension, periodic) bottom_neighbor = _bottom_neighbor(site, x_dimension, y_dimension, periodic) # Avoid double-counting edges when one of the dimensions is 2 # and the system is periodic if x_dimension == 2 and periodic and site % 2 == 1: right_neighbor = None if y_dimension == 2 and periodic and site >= x_dimension: bottom_neighbor = None # Add terms that couple with neighbors to the right and bottom. if right_neighbor is not None: # Add hopping term hubbard_model += _hopping_term(site, right_neighbor, -tunneling, bosonic=True) # Add local Coulomb interaction term hubbard_model += _coulomb_interaction_term( n_sites, site, right_neighbor, dipole, particle_hole_symmetry=False, bosonic=True) if bottom_neighbor is not None: # Add hopping term hubbard_model += _hopping_term(site, bottom_neighbor, -tunneling, bosonic=True) # Add local Coulomb interaction term hubbard_model += _coulomb_interaction_term( n_sites, site, bottom_neighbor, dipole, particle_hole_symmetry=False, bosonic=True) # Add on-site interaction. hubbard_model += ( number_operator(n_sites, site, 0.5 * interaction, parity=1) * (number_operator(n_sites, site, parity=1) - BosonOperator(()))) # Add chemical potential. hubbard_model += number_operator(n_sites, site, -chemical_potential, parity=1) return hubbard_model
def test_number_operator_site(self): op = number_operator(3, 2, 1j) self.assertTrue(op.isclose(FermionOperator(((2, 1), (2, 0))) * 1j))