def expectation_one_body_db_operator_computational_basis_state( dual_basis_action, plane_wave_occ_orbitals, grid, spinless): """Compute expectation value of a 1-body dual-basis operator with a plane wave computational basis state. Args: dual_basis_action: Dual-basis action of FermionOperator to evaluate expectation value of. plane_wave_occ_orbitals (list): list of occupied plane-wave orbitals. grid (openfermion.utils.Grid): The grid used for discretization. spinless (bool): Whether the system is spinless. Returns: A real float giving the expectation value. """ expectation_value = 0.0 r_p = position_vector(grid_indices(dual_basis_action[0][0], grid, spinless), grid) r_q = position_vector(grid_indices(dual_basis_action[1][0], grid, spinless), grid) for orbital in plane_wave_occ_orbitals: # If there's spin, p and q have to have the same parity (spin), # and the new orbital has to have the same spin as these. k_orbital = momentum_vector(grid_indices(orbital, grid, spinless), grid) # The Fourier transform is spin-conserving. This means that p, q, # and the new orbital all have to have the same spin (parity). if spinless or (dual_basis_action[0][0] % 2 == dual_basis_action[1][0] % 2 == orbital % 2): expectation_value += numpy.exp(-1j * k_orbital.dot(r_p - r_q)) return expectation_value
def test_position_vector(self): # Test in 1D. grid = Grid(dimensions=1, length=4, scale=4.) test_output = [position_vector(i, grid) for i in range(grid.length)] correct_output = [-2, -1, 0, 1] self.assertEqual(correct_output, test_output) grid = Grid(dimensions=1, length=11, scale=2. * numpy.pi) for i in range(grid.length): self.assertAlmostEqual(-position_vector(i, grid), position_vector(grid.length - i - 1, grid)) # Test in 2D. grid = Grid(dimensions=2, length=3, scale=3.) test_input = [] test_output = [] for i in range(3): for j in range(3): test_input += [(i, j)] test_output += [position_vector((i, j), grid)] correct_output = numpy.array([[-1., -1.], [-1., 0.], [-1., 1.], [0., -1.], [0., 0.], [0., 1.], [1., -1.], [1., 0.], [1., 1.]]) self.assertAlmostEqual(0., numpy.amax(test_output - correct_output))
def jordan_wigner_dual_basis_hamiltonian(grid, geometry=None, spinless=False, include_constant=False): """Return the dual basis Hamiltonian as QubitOperator. Args: grid (Grid): The discretization to use. geometry: A list of tuples giving the coordinates of each atom. example is [('H', (0, 0, 0)), ('H', (0, 0, 0.7414))]. Distances in atomic units. Use atomic symbols to specify atoms. spinless (bool): Whether to use the spinless model or not. include_constant (bool): Whether to include the Madelung constant. Returns: hamiltonian (QubitOperator) """ jellium_op = jordan_wigner_dual_basis_jellium(grid, spinless, include_constant) if geometry is None: return jellium_op for item in geometry: if len(item[1]) != grid.dimensions: raise ValueError("Invalid geometry coordinate.") if item[0] not in periodic_hash_table: raise ValueError("Invalid nuclear element.") n_orbitals = grid.num_points() volume = grid.volume_scale() if spinless: n_qubits = n_orbitals else: n_qubits = 2 * n_orbitals prefactor = -2 * numpy.pi / volume external_potential = QubitOperator() for k_indices in grid.all_points_indices(): momenta = momentum_vector(k_indices, grid) momenta_squared = momenta.dot(momenta) if momenta_squared < EQ_TOLERANCE: continue for p in range(n_qubits): index_p = grid_indices(p, grid, spinless) coordinate_p = position_vector(index_p, grid) for nuclear_term in geometry: coordinate_j = numpy.array(nuclear_term[1], float) exp_index = 1.0j * momenta.dot(coordinate_j - coordinate_p) coefficient = (prefactor / momenta_squared * periodic_hash_table[nuclear_term[0]] * numpy.exp(exp_index)) external_potential += (QubitOperator( (), coefficient) - QubitOperator( ((p, 'Z'), ), coefficient)) return jellium_op + external_potential
def dual_basis_external_potential(grid, geometry, spinless): """Return the external potential in the dual basis of arXiv:1706.00023. Args: grid (Grid): The discretization to use. geometry: A list of tuples giving the coordinates of each atom. example is [('H', (0, 0, 0)), ('H', (0, 0, 0.7414))]. Distances in atomic units. Use atomic symbols to specify atoms. spinless (bool): Whether to use the spinless model or not. Returns: FermionOperator: The dual basis operator. """ prefactor = -4.0 * numpy.pi / grid.volume_scale() operator = None if spinless: spins = [None] else: spins = [0, 1] for pos_indices in grid.all_points_indices(): coordinate_p = position_vector(pos_indices, grid) for nuclear_term in geometry: coordinate_j = numpy.array(nuclear_term[1], float) for momenta_indices in grid.all_points_indices(): momenta = momentum_vector(momenta_indices, grid) momenta_squared = momenta.dot(momenta) if momenta_squared < EQ_TOLERANCE: continue exp_index = 1.0j * momenta.dot(coordinate_j - coordinate_p) coefficient = (prefactor / momenta_squared * periodic_hash_table[nuclear_term[0]] * numpy.exp(exp_index)) for spin_p in spins: orbital_p = orbital_id(grid, pos_indices, spin_p) operators = ((orbital_p, 1), (orbital_p, 0)) if operator is None: operator = FermionOperator(operators, coefficient) else: operator += FermionOperator(operators, coefficient) return operator
def expectation_three_body_db_operator_computational_basis_state( dual_basis_action, plane_wave_occ_orbitals, grid, spinless): """Compute expectation value of a 3-body dual-basis operator with a plane wave computational basis state. Args: dual_basis_action: Dual-basis action of FermionOperator to evaluate expectation value of. plane_wave_occ_orbitals (list): list of occupied plane-wave orbitals. grid (openfermion.utils.Grid): The grid used for discretization. spinless (bool): Whether the system is spinless. Returns: A float giving the expectation value. """ expectation_value = 0.0 r = {} for i in range(6): r[i] = position_vector(grid_indices(dual_basis_action[i][0], grid, spinless), grid) rr = {} k_map = {} for i in range(3): rr[i] = {} k_map[i] = {} for j in range(3, 6): rr[i][j] = r[i] - r[j] k_map[i][j] = {} # Pre-computations. for o in plane_wave_occ_orbitals: k = momentum_vector(grid_indices(o, grid, spinless), grid) for i in range(3): for j in range(3, 6): k_map[i][j][o] = k.dot(rr[i][j]) for orbital1 in plane_wave_occ_orbitals: k1ad = k_map[0][3][orbital1] k1ae = k_map[0][4][orbital1] k1af = k_map[0][5][orbital1] for orbital2 in plane_wave_occ_orbitals: if orbital1 != orbital2: k2bd = k_map[1][3][orbital2] k2be = k_map[1][4][orbital2] k2bf = k_map[1][5][orbital2] for orbital3 in plane_wave_occ_orbitals: if orbital1 != orbital3 and orbital2 != orbital3: k3cd = k_map[2][3][orbital3] k3ce = k_map[2][4][orbital3] k3cf = k_map[2][5][orbital3] # Handle \delta_{ad} \delta_{bf} \delta_{ce} after FT. # The Fourier transform is spin-conserving. if spinless or ( (dual_basis_action[0][0] % 2 == dual_basis_action[3][0] % 2 == orbital1 % 2) and (dual_basis_action[1][0] % 2 == dual_basis_action[5][0] % 2 == orbital2 % 2) and (dual_basis_action[2][0] % 2 == dual_basis_action[4][0] % 2 == orbital3 % 2)): expectation_value += numpy.exp(-1j * ( k1ad + k2bf + k3ce)) # Handle -\delta_{ad} \delta_{be} \delta_{cf} after FT. # The Fourier transform is spin-conserving. if spinless or ( (dual_basis_action[0][0] % 2 == dual_basis_action[3][0] % 2 == orbital1 % 2) and (dual_basis_action[1][0] % 2 == dual_basis_action[4][0] % 2 == orbital2 % 2) and (dual_basis_action[2][0] % 2 == dual_basis_action[5][0] % 2 == orbital3 % 2)): expectation_value -= numpy.exp(-1j * ( k1ad + k2be + k3cf)) # Handle -\delta_{ae} \delta_{bf} \delta_{cd} after FT. # The Fourier transform is spin-conserving. if spinless or ( (dual_basis_action[0][0] % 2 == dual_basis_action[4][0] % 2 == orbital1 % 2) and (dual_basis_action[1][0] % 2 == dual_basis_action[5][0] % 2 == orbital2 % 2) and (dual_basis_action[2][0] % 2 == dual_basis_action[3][0] % 2 == orbital3 % 2)): expectation_value -= numpy.exp(-1j * ( k1ae + k2bf + k3cd)) # Handle \delta_{ae} \delta_{bd} \delta_{cf} after FT. # The Fourier transform is spin-conserving. if spinless or ( (dual_basis_action[0][0] % 2 == dual_basis_action[4][0] % 2 == orbital1 % 2) and (dual_basis_action[1][0] % 2 == dual_basis_action[3][0] % 2 == orbital2 % 2) and (dual_basis_action[2][0] % 2 == dual_basis_action[5][0] % 2 == orbital3 % 2)): expectation_value += numpy.exp(-1j * ( k1ae + k2bd + k3cf)) # Handle \delta_{af} \delta_{be} \delta_{cd} after FT. # The Fourier transform is spin-conserving. if spinless or ( (dual_basis_action[0][0] % 2 == dual_basis_action[5][0] % 2 == orbital1 % 2) and (dual_basis_action[1][0] % 2 == dual_basis_action[4][0] % 2 == orbital2 % 2) and (dual_basis_action[2][0] % 2 == dual_basis_action[3][0] % 2 == orbital3 % 2)): expectation_value += numpy.exp(-1j * ( k1af + k2be + k3cd)) # Handle -\delta_{af} \delta_{bd} \delta_{ce} after FT. # The Fourier transform is spin-conserving. if spinless or ( (dual_basis_action[0][0] % 2 == dual_basis_action[5][0] % 2 == orbital1 % 2) and (dual_basis_action[1][0] % 2 == dual_basis_action[3][0] % 2 == orbital2 % 2) and (dual_basis_action[2][0] % 2 == dual_basis_action[4][0] % 2 == orbital3 % 2)): expectation_value -= numpy.exp(-1j * ( k1af + k2bd + k3ce)) return expectation_value
def expectation_two_body_db_operator_computational_basis_state( dual_basis_action, plane_wave_occ_orbitals, grid, spinless): """Compute expectation value of a 2-body dual-basis operator with a plane wave computational basis state. Args: dual_basis_action: Dual-basis action of FermionOperator to evaluate expectation value of. plane_wave_occ_orbitals (list): list of occupied plane-wave orbitals. grid (openfermion.utils.Grid): The grid used for discretization. spinless (bool): Whether the system is spinless. Returns: A float giving the expectation value. """ expectation_value = 0.0 r = {} for i in range(4): r[i] = position_vector(grid_indices(dual_basis_action[i][0], grid, spinless), grid) rr = {} k_map = {} for i in range(2): rr[i] = {} k_map[i] = {} for j in range(2, 4): rr[i][j] = r[i] - r[j] k_map[i][j] = {} # Pre-computations. for o in plane_wave_occ_orbitals: k = momentum_vector(grid_indices(o, grid, spinless), grid) for i in range(2): for j in range(2, 4): k_map[i][j][o] = k.dot(rr[i][j]) for orbital1 in plane_wave_occ_orbitals: k1ac = k_map[0][2][orbital1] k1ad = k_map[0][3][orbital1] for orbital2 in plane_wave_occ_orbitals: if orbital1 != orbital2: k2bc = k_map[1][2][orbital2] k2bd = k_map[1][3][orbital2] # The Fourier transform is spin-conserving. This means that # the parity of the orbitals involved in the transition must # be the same. if spinless or ( (dual_basis_action[0][0] % 2 == dual_basis_action[3][0] % 2 == orbital1 % 2) and (dual_basis_action[1][0] % 2 == dual_basis_action[2][0] % 2 == orbital2 % 2)): value = numpy.exp(-1j * (k1ad + k2bc)) # Add because it came from two anti-commutations. expectation_value += value # The Fourier transform is spin-conserving. This means that # the parity of the orbitals involved in the transition must # be the same. if spinless or ( (dual_basis_action[0][0] % 2 == dual_basis_action[2][0] % 2 == orbital1 % 2) and (dual_basis_action[1][0] % 2 == dual_basis_action[3][0] % 2 == orbital2 % 2)): value = numpy.exp(-1j * (k1ac + k2bd)) # Subtract because it came from a single anti-commutation. expectation_value -= value return expectation_value
def test_coefficients(self): # Test that the coefficients post-JW transform are as claimed in paper. grid = Grid(dimensions=2, length=3, scale=2.) spinless = 1 n_orbitals = grid.num_points() n_qubits = (2**(1 - spinless)) * n_orbitals volume = grid.volume_scale() # Kinetic operator. kinetic = dual_basis_kinetic(grid, spinless) qubit_kinetic = jordan_wigner(kinetic) # Potential operator. potential = dual_basis_potential(grid, spinless) qubit_potential = jordan_wigner(potential) # Check identity. identity = tuple() kinetic_coefficient = qubit_kinetic.terms[identity] potential_coefficient = qubit_potential.terms[identity] paper_kinetic_coefficient = 0. paper_potential_coefficient = 0. for indices in grid.all_points_indices(): momenta = momentum_vector(indices, grid) paper_kinetic_coefficient += float(n_qubits) * momenta.dot( momenta) / float(4. * n_orbitals) if momenta.any(): potential_contribution = -numpy.pi * float(n_qubits) / float( 2. * momenta.dot(momenta) * volume) paper_potential_coefficient += potential_contribution self.assertAlmostEqual(kinetic_coefficient, paper_kinetic_coefficient) self.assertAlmostEqual(potential_coefficient, paper_potential_coefficient) # Check Zp. for p in range(n_qubits): zp = ((p, 'Z'), ) kinetic_coefficient = qubit_kinetic.terms[zp] potential_coefficient = qubit_potential.terms[zp] paper_kinetic_coefficient = 0. paper_potential_coefficient = 0. for indices in grid.all_points_indices(): momenta = momentum_vector(indices, grid) paper_kinetic_coefficient -= momenta.dot(momenta) / float( 4. * n_orbitals) if momenta.any(): potential_contribution = numpy.pi / float( momenta.dot(momenta) * volume) paper_potential_coefficient += potential_contribution self.assertAlmostEqual(kinetic_coefficient, paper_kinetic_coefficient) self.assertAlmostEqual(potential_coefficient, paper_potential_coefficient) # Check Zp Zq. if spinless: spins = [None] else: spins = [0, 1] for indices_a in grid.all_points_indices(): for indices_b in grid.all_points_indices(): paper_kinetic_coefficient = 0. paper_potential_coefficient = 0. position_a = position_vector(indices_a, grid) position_b = position_vector(indices_b, grid) differences = position_b - position_a for spin_a in spins: for spin_b in spins: p = orbital_id(grid, indices_a, spin_a) q = orbital_id(grid, indices_b, spin_b) if p == q: continue zpzq = ((min(p, q), 'Z'), (max(p, q), 'Z')) if zpzq in qubit_potential.terms: potential_coefficient = qubit_potential.terms[zpzq] for indices_c in grid.all_points_indices(): momenta = momentum_vector(indices_c, grid) if momenta.any(): potential_contribution = numpy.pi * numpy.cos( differences.dot(momenta)) / float( momenta.dot(momenta) * volume) paper_potential_coefficient += ( potential_contribution) self.assertAlmostEqual(potential_coefficient, paper_potential_coefficient)