def test_jordan_wigner_dual_basis_jellium_constant_shift(self): length_scale = 0.6 grid = Grid(dimensions=2, length=3, scale=length_scale) spinless = True hamiltonian_without_constant = jordan_wigner_dual_basis_jellium( grid, spinless, include_constant=False) hamiltonian_with_constant = jordan_wigner_dual_basis_jellium( grid, spinless, include_constant=True) difference = hamiltonian_with_constant - hamiltonian_without_constant expected = FermionOperator.identity() * (2.8372 / length_scale) self.assertTrue(expected.isclose(difference))
def jellium_model(grid, spinless=False, plane_wave=True, include_constant=False): """Return jellium Hamiltonian as FermionOperator class. Args: grid (fermilib.utils.Grid): The discretization to use. spinless (bool): Whether to use the spinless model or not. plane_wave (bool): Whether to return in momentum space (True) or position space (False). include_constant (bool): Whether to include the Madelung constant. Returns: FermionOperator: The Hamiltonian of the model. """ if plane_wave: hamiltonian = plane_wave_kinetic(grid, spinless) hamiltonian += plane_wave_potential(grid, spinless) else: hamiltonian = dual_basis_jellium_model(grid, spinless) # Include the Madelung constant if requested. if include_constant: hamiltonian += FermionOperator.identity() * (2.8372 / grid.scale) return hamiltonian
def _fourier_transform_helper(hamiltonian, grid, spinless, phase_factor, vec_func_1, vec_func_2): hamiltonian_t = FermionOperator.zero() normalize_factor = numpy.sqrt(1.0 / float(grid.num_points())) for term in hamiltonian.terms: transformed_term = FermionOperator.identity() for ladder_op_mode, ladder_op_type in term: indices_1 = grid_indices(ladder_op_mode, grid, spinless) vec1 = vec_func_1(indices_1, grid) new_basis = FermionOperator.zero() for indices_2 in grid.all_points_indices(): vec2 = vec_func_2(indices_2, grid) spin = None if spinless else ladder_op_mode % 2 orbital = orbital_id(grid, indices_2, spin) exp_index = phase_factor * 1.0j * numpy.dot(vec1, vec2) if ladder_op_type == 1: exp_index *= -1.0 element = FermionOperator(((orbital, ladder_op_type),), numpy.exp(exp_index)) new_basis += element new_basis *= normalize_factor transformed_term *= new_basis # Coefficient. transformed_term *= hamiltonian.terms[term] hamiltonian_t += transformed_term return hamiltonian_t
def dual_basis_jellium_model(grid, spinless=False, kinetic=True, potential=True, include_constant=False): """Return jellium Hamiltonian in the dual basis of arXiv:1706.00023 Args: grid (Grid): The discretization to use. spinless (bool): Whether to use the spinless model or not. kinetic (bool): Whether to include kinetic terms. potential (bool): Whether to include potential terms. include_constant (bool): Whether to include the Madelung constant. Returns: operator (FermionOperator) """ # Initialize. n_points = grid.num_points() position_prefactor = 2. * numpy.pi / grid.volume_scale() operator = FermionOperator() spins = [None] if spinless else [0, 1] # Pre-Computations. position_vectors = {} momentum_vectors = {} momenta_squared_dict = {} orbital_ids = {} for indices in grid.all_points_indices(): position_vectors[indices] = position_vector(indices, grid) momenta = momentum_vector(indices, grid) momentum_vectors[indices] = momenta momenta_squared_dict[indices] = momenta.dot(momenta) orbital_ids[indices] = {} for spin in spins: orbital_ids[indices][spin] = orbital_id(grid, indices, spin) # Loop once through all lattice sites. for grid_indices_a in grid.all_points_indices(): coordinates_a = position_vectors[grid_indices_a] for grid_indices_b in grid.all_points_indices(): coordinates_b = position_vectors[grid_indices_b] differences = coordinates_b - coordinates_a # Compute coefficients. kinetic_coefficient = 0. potential_coefficient = 0. for momenta_indices in grid.all_points_indices(): momenta = momentum_vectors[momenta_indices] momenta_squared = momenta_squared_dict[momenta_indices] if momenta_squared == 0: continue cos_difference = numpy.cos(momenta.dot(differences)) if kinetic: kinetic_coefficient += ( cos_difference * momenta_squared / (2. * float(n_points))) if potential: potential_coefficient += ( position_prefactor * cos_difference / momenta_squared) # Loop over spins and identify interacting orbitals. orbital_a = {} orbital_b = {} for spin in spins: orbital_a[spin] = orbital_ids[grid_indices_a][spin] orbital_b[spin] = orbital_ids[grid_indices_b][spin] if kinetic: for spin in spins: operators = ((orbital_a[spin], 1), (orbital_b[spin], 0)) operator += FermionOperator(operators, kinetic_coefficient) if potential: for sa in spins: for sb in spins: if orbital_a[sa] == orbital_b[sb]: continue operators = ((orbital_a[sa], 1), (orbital_a[sa], 0), (orbital_b[sb], 1), (orbital_b[sb], 0)) operator += FermionOperator(operators, potential_coefficient) # Include the Madelung constant if requested. if include_constant: operator += FermionOperator.identity() * (2.8372 / grid.scale) # Return. return operator
def hartree_fock_state_jellium(grid, n_electrons, spinless=True, plane_wave=False): """Give the Hartree-Fock state of jellium. Args: grid (Grid): The discretization to use. n_electrons (int): Number of electrons in the system. spinless (bool): Whether to use the spinless model or not. plane_wave (bool): Whether to return the Hartree-Fock state in the plane wave (True) or dual basis (False). Notes: The jellium model is built up by filling the lowest-energy single-particle states in the plane-wave Hamiltonian until n_electrons states are filled. """ # Get the jellium Hamiltonian in the plane wave basis. hamiltonian = jellium_model(grid, spinless, plane_wave=True) hamiltonian = normal_ordered(hamiltonian) hamiltonian.compress() # Enumerate the single-particle states. n_single_particle_states = (grid.length**grid.dimensions) if not spinless: n_single_particle_states *= 2 # Compute the energies for each of the single-particle states. single_particle_energies = numpy.zeros(n_single_particle_states, dtype=float) for i in range(n_single_particle_states): single_particle_energies[i] = hamiltonian.terms.get(((i, 1), (i, 0)), 0.0) # The number of occupied single-particle states is the number of electrons. # Those states with the lowest single-particle energies are occupied first. occupied_states = single_particle_energies.argsort()[:n_electrons] if plane_wave: # In the plane wave basis the HF state is a single determinant. hartree_fock_state_index = numpy.sum(2**occupied_states) hartree_fock_state = csr_matrix( ([1.0], ([hartree_fock_state_index], [0])), shape=(2**n_single_particle_states, 1)) else: # Inverse Fourier transform the creation operators for the state to get # to the dual basis state, then use that to get the dual basis state. hartree_fock_state_creation_operator = FermionOperator.identity() for state in occupied_states[::-1]: hartree_fock_state_creation_operator *= (FermionOperator( ((int(state), 1), ))) dual_basis_hf_creation_operator = inverse_fourier_transform( hartree_fock_state_creation_operator, grid, spinless) dual_basis_hf_creation = normal_ordered( dual_basis_hf_creation_operator) # Initialize the HF state as a sparse matrix. hartree_fock_state = csr_matrix(([], ([], [])), shape=(2**n_single_particle_states, 1), dtype=complex) # Populate the elements of the HF state in the dual basis. for term in dual_basis_hf_creation.terms: index = 0 for operator in term: index += 2**operator[0] hartree_fock_state[index, 0] = dual_basis_hf_creation.terms[term] return hartree_fock_state