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 jellium_model(grid, spinless=False, plane_wave=True, include_constant=False, e_cutoff=None): """Return jellium Hamiltonian as FermionOperator class. Args: grid (openfermion.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. Note constant is unsupported for non-uniform, non-cubic cells with ions. e_cutoff (float): Energy cutoff. Returns: FermionOperator: The Hamiltonian of the model. """ if plane_wave: hamiltonian = plane_wave_kinetic(grid, spinless, e_cutoff) hamiltonian += plane_wave_potential(grid, spinless, e_cutoff) else: hamiltonian = dual_basis_jellium_model(grid, spinless) # Include the Madelung constant if requested. if include_constant: # TODO: Check for other unit cell shapes hamiltonian += (FermionOperator.identity() * (2.8372 / grid.volume_scale()**(1. / grid.dimensions))) return hamiltonian
def test_commutes_identity(self): com = commutator(FermionOperator.identity(), FermionOperator('2^ 3', 2.3)) self.assertEqual(com, FermionOperator.zero()) com = commutator(BosonOperator.identity(), BosonOperator('2^ 3', 2.3)) self.assertTrue(com == BosonOperator.zero()) com = commutator(QuadOperator.identity(), QuadOperator('q2 p3', 2.3)) self.assertTrue(com == QuadOperator.zero())
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 test_canonical_anticommutation_relations(self): op_1 = FermionOperator('3') op_1_dag = FermionOperator('3^') op_2 = FermionOperator('4') op_2_dag = FermionOperator('4^') zero = FermionOperator.zero() one = FermionOperator.identity() self.assertEqual(one, normal_ordered(anticommutator(op_1, op_1_dag))) self.assertEqual(zero, normal_ordered(anticommutator(op_1, op_2))) self.assertEqual(zero, normal_ordered(anticommutator(op_1, op_2_dag))) self.assertEqual(zero, normal_ordered(anticommutator(op_1_dag, op_2))) self.assertEqual(zero, normal_ordered(anticommutator(op_1_dag, op_2_dag))) self.assertEqual(one, normal_ordered(anticommutator(op_2, op_2_dag)))
def jellium_model(grid, spinless=False, plane_wave=True, include_constant=False, e_cutoff=None, non_periodic=False, period_cutoff=None, fieldlines=3, R0=1e8, verbose=False): """Return jellium Hamiltonian as FermionOperator class. Args: grid (openfermion.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. Note constant is unsupported for non-uniform, non-cubic cells with ions. e_cutoff (float): Energy cutoff. non_periodic (bool): If the system is non-periodic, default to False. period_cutoff (float): Period cutoff, default to grid.volume_scale() ** (1. / grid.dimensions). fieldlines (int): Spatial dimension for electric field lines. R0 (float): Reference length scale where the 2D Coulomb potential is zero. verbose (bool): Whether to turn on print statements. Returns: FermionOperator: The Hamiltonian of the model. """ if grid.dimensions == 1: raise ValueError('System dimension cannot be 1.') if plane_wave: hamiltonian = plane_wave_kinetic(grid, spinless, e_cutoff) hamiltonian += plane_wave_potential( grid, spinless, e_cutoff, non_periodic, period_cutoff, fieldlines, R0, verbose) else: hamiltonian = dual_basis_jellium_model( grid, spinless, True, True, include_constant, non_periodic, period_cutoff, fieldlines, R0, verbose) # Include the Madelung constant if requested. if include_constant: # TODO: Check for other unit cell shapes hamiltonian += (FermionOperator.identity() * (2.8372 / grid.volume_scale()**(1. / grid.dimensions))) return hamiltonian
def jellium_model(grid, spinless=False, plane_wave=True, include_constant=False): """Return jellium Hamiltonian as FermionOperator class. Args: grid (openfermion.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 dual_basis_jellium_model(grid, spinless=False, kinetic=True, potential=True, include_constant=False, non_periodic=False, period_cutoff=None): """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. Note constant is unsupported for non-uniform, non-cubic cells with ions. non_periodic (bool): If the system is non-periodic, default to False. period_cutoff (float): Period cutoff, default to grid.volume_scale() ** (1. / grid.dimensions). Returns: operator (FermionOperator) """ # Initialize. n_points = grid.num_points position_prefactor = 2.0 * numpy.pi / grid.volume_scale() operator = FermionOperator() spins = [None] if spinless else [0, 1] if potential and non_periodic and period_cutoff is None: period_cutoff = grid.volume_scale()**(1.0 / grid.dimensions) # Pre-Computations. position_vectors = {} momentum_vectors = {} momenta_squared_dict = {} orbital_ids = {} for indices in grid.all_points_indices(): position_vectors[indices] = grid.position_vector(indices) momenta = grid.momentum_vector(indices) momentum_vectors[indices] = momenta momenta_squared_dict[indices] = momenta.dot(momenta) orbital_ids[indices] = {} for spin in spins: orbital_ids[indices][spin] = grid.orbital_id(indices, spin) # Loop once through all lattice sites. grid_origin = (0, ) * grid.dimensions coordinates_origin = position_vectors[grid_origin] for grid_indices_b in grid.all_points_indices(): coordinates_b = position_vectors[grid_indices_b] differences = coordinates_b - coordinates_origin # 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) for grid_indices_shift in grid.all_points_indices(): # Loop over spins and identify interacting orbitals. orbital_a = {} orbital_b = {} shifted_index_1 = tuple([ (grid_origin[i] + grid_indices_shift[i]) % grid.length[i] for i in range(grid.dimensions) ]) shifted_index_2 = tuple([ (grid_indices_b[i] + grid_indices_shift[i]) % grid.length[i] for i in range(grid.dimensions) ]) for spin in spins: orbital_a[spin] = orbital_ids[shifted_index_1][spin] orbital_b[spin] = orbital_ids[shifted_index_2][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: # TODO: Check for other unit cell shapes operator += (FermionOperator.identity() * (2.8372 / grid.volume_scale()**(1. / grid.dimensions))) # Return. return operator
def test_expectation_state_is_list_identity_fermion_operator(self): operator = FermionOperator.identity() * 1.1 state = [0, 1, 1] self.assertAlmostEqual( expectation_computational_basis_state(operator, state), 1.1)
def test_expectation_identity_fermion_operator(self): operator = FermionOperator.identity() * 1.1 state = csc_matrix(([1], ([6], [0])), shape=(16, 1)) self.assertAlmostEqual( expectation_computational_basis_state(operator, state), 1.1)
def test_commutes_identity(self): com = commutator(FermionOperator.identity(), FermionOperator('2^ 3', 2.3)) self.assertTrue(com.isclose(FermionOperator.zero()))
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. """ from openfermion.hamiltonians import plane_wave_kinetic # Get the jellium Hamiltonian in the plane wave basis. # For determining the Hartree-Fock state in the PW basis, only the # kinetic energy terms matter. hamiltonian = plane_wave_kinetic(grid, spinless=spinless) hamiltonian = normal_ordered(hamiltonian) hamiltonian.compress() # 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 = lowest_single_particle_energy_states( hamiltonian, n_electrons) occupied_states = numpy.array(occupied_states) 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 = numpy.zeros(2**count_qubits(hamiltonian), dtype=complex) hartree_fock_state[hartree_fock_state_index] = 1.0 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. hartree_fock_state = numpy.zeros(2**count_qubits(hamiltonian), 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] = dual_basis_hf_creation.terms[term] return hartree_fock_state
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 test_commutes_identity(self): com = commutator(FermionOperator.identity(), FermionOperator('2^ 3', 2.3)) self.assertEqual(com, FermionOperator.zero())
def dual_basis_jellium_model(grid, spinless=False, kinetic=True, potential=True, include_constant=False, non_periodic=False, period_cutoff=None, fieldlines=3, R0=1e8, verbose=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. Note constant is unsupported for non-uniform, non-cubic cells with ions. non_periodic (bool): If the system is non-periodic, default to False. period_cutoff (float): Period cutoff, default to grid.volume_scale() ** (1. / grid.dimensions). fieldlines (int): Spatial dimension for electric field lines. R0 (float): Reference length scale where the 2D Coulomb potential is zero. verbose (bool): Whether to turn on print statements. Returns: operator (FermionOperator) """ if potential == True and grid.dimensions == 1: raise ValueError('System dimension cannot be 1.') # Initialize. n_points = grid.num_points position_prefactor = 0. # 3D case. if grid.dimensions == 3: position_prefactor = 2.0 * numpy.pi / grid.volume_scale() # 2D case. elif grid.dimensions == 2: position_prefactor = 1. / (2. * grid.volume_scale()) operator = FermionOperator() spins = [None] if spinless else [0, 1] if potential and non_periodic and period_cutoff is None: period_cutoff = grid.volume_scale() ** (1.0 / grid.dimensions) # Pre-Computations. position_vectors = {} momentum_vectors = {} momenta_squared_dict = {} orbital_ids = {} for indices in grid.all_points_indices(): # Store position vectors in dictionary, with corresponding # grid index as key. position_vectors[indices] = grid.position_vector(indices) # Get and store momentum vectors in dictionary, with corresponding # grid index as key. momenta = grid.momentum_vector(indices) momentum_vectors[indices] = momenta # Store momentum squared in dictionary, with corresponding # grid index as key. momenta_squared_dict[indices] = momenta.dot(momenta) # Store spin orbitals at each grid index in dictionary. orbital_ids[indices] = {} for spin in spins: orbital_ids[indices][spin] = grid.orbital_id(indices, spin) # This gives the position vector of the grid point at bottom-left-most # corner. # # x---x---x # | | | # x---x---x # | | | # this point <-- x---x---x # grid_origin = (0, ) * grid.dimensions coordinates_origin = position_vectors[grid_origin] # Loop once through all grid points. for grid_indices_b in grid.all_points_indices(): if verbose: print('Grid point: {}\n'.format(grid_indices_b)) # For all grid points, 'differences' gets the position displacement # from the 'origin' point. This corresponds to evaluating # (r_p - r_q) == r_(p-q). coordinates_b = position_vectors[grid_indices_b] differences = coordinates_b - coordinates_origin # Compute coefficients. kinetic_coefficient = 0. potential_coefficient = 0. # Loop once through all momentum indices, k_nu. 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)) # This computes 1/(2N) * sum_nu{ k_nu^2 * cos[k_nu * r_(q-p)] } if kinetic: if verbose: print('Added kinetic term.') kinetic_coefficient += ( cos_difference * momenta_squared / (2. * float(n_points))) if verbose: print('Potential = {}'.format(potential)) # This computes 2pi/Omega * sum_nu{ cos[k_nu * r_(p-q)] / k_nu^2 } if potential: # Potential coefficient for this value of nu. potential_coefficient_nu = 0. # 3D case. if grid.dimensions == 3: potential_coefficient_nu = ( position_prefactor * cos_difference / momenta_squared) # If non-periodic. if non_periodic: correction = 1.0 - numpy.cos( period_cutoff * numpy.sqrt(momenta_squared)) potential_coefficient_nu *= correction if verbose: print('non_periodic') print('cutoff: {}'.format(period_cutoff)) print('correction: {}\n'.format(correction)) # 2D case. elif grid.dimensions == 2: V_nu = 0. # 2D Coulomb potential. if fieldlines == 2: # If non-periodic. if non_periodic: Dkv = period_cutoff * numpy.sqrt(momenta_squared) V_nu = ( 2. * numpy.pi / momenta_squared * ( Dkv * numpy.log(R0 / period_cutoff) * scipy.special.jv(1, Dkv) - scipy.special.jv(0, Dkv))) if verbose: print('non-periodic') print('cutoff: {}\n'.format(period_cutoff)) print('RO = {}'.format(R0)) # If periodic. else: var1 = 4. / momenta_squared var2 = 0.25 * momenta_squared V_nu = 0.5 * numpy.complex128( mpmath.meijerg([[1., 1.5, 2.], []], [[1.5], []], var1) - mpmath.meijerg([[-0.5, 0., 0.], []], [[-0.5, 0.], [-1.]], var2)) # 3D Coulomb potential. elif fieldlines == 3: # If non-periodic. if non_periodic: var = -0.25 * period_cutoff**2 * momenta_squared V_nu = numpy.complex128( 2 * numpy.pi * period_cutoff * mpmath.hyp1f2(0.5, 1., 1.5, var)) if verbose: print('non-periodic') print('cutoff: {}\n'.format(period_cutoff)) # If periodic. else: V_nu = 2. * numpy.pi / numpy.sqrt(momenta_squared) # Potential coefficient for this value of nu. potential_coefficient_nu = ( position_prefactor * V_nu * cos_difference) potential_coefficient += potential_coefficient_nu if verbose: print('fieldlines = {}'.format(fieldlines)) print('potential coefficient nu: {}\n'.format(potential_coefficient_nu)) if verbose: print('kinetic coefficient: {}'.format(kinetic_coefficient)) print('potential coefficient: {}\n'.format(potential_coefficient)) # Loop once through all grid points. # We have r_p - r_q fixed by 'differences' computed above. for grid_indices_shift in grid.all_points_indices(): # Loop over spins and identify interacting orbitals. orbital_a = {} orbital_b = {} # grid_origin = (0, ) * grid.dimensions # 'shifted_index_1' is equivalent to just 'grid_indices_shift'. shifted_index_1 = tuple( [(grid_origin[i] + grid_indices_shift[i]) % grid.length[i] for i in range(grid.dimensions)]) # 'shifted_index_2' shifted_index_2 = tuple( [(grid_indices_b[i] + grid_indices_shift[i]) % grid.length[i] for i in range(grid.dimensions)]) if verbose: print('shifted index 1: {}'.format(shifted_index_1)) print('shifted index 2: {}'.format(shifted_index_2)) for spin in spins: orbital_a[spin] = orbital_ids[shifted_index_1][spin] orbital_b[spin] = orbital_ids[shifted_index_2][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: # TODO: Check for other unit cell shapes # Currently only for cubic cells. operator += (FermionOperator.identity() * (2.8372 / grid.volume_scale()**(1./grid.dimensions))) # Return. return operator