def test_hf_state_plane_wave_basis_lowest_single_determinant_state(self): grid_length = 7 dimension = 1 spinless = True n_particles = 4 length_scale = 2.0 grid = Grid(dimension, grid_length, length_scale) hamiltonian = jellium_model(grid, spinless) hamiltonian_sparse = get_sparse_operator(hamiltonian) hf_state = hartree_fock_state_jellium(grid, n_particles, spinless, plane_wave=True) HF_energy = expectation(hamiltonian_sparse, hf_state) for occupied_orbitals in permutations([1] * n_particles + [0] * (grid_length - n_particles)): state_index = numpy.sum(2**numpy.array(occupied_orbitals)) HF_competitor = numpy.zeros(2**grid_length) HF_competitor[state_index] = 1.0 self.assertLessEqual( HF_energy, expectation(hamiltonian_sparse, HF_competitor))
def test_hf_state_energy_close_to_ground_energy_at_high_density(self): grid_length = 8 dimension = 1 spinless = True n_particles = grid_length**dimension // 2 # High density -> small length_scale. length_scale = 0.25 grid = Grid(dimension, grid_length, length_scale) hamiltonian = jellium_model(grid, spinless) hamiltonian_sparse = get_sparse_operator(hamiltonian) hf_state = hartree_fock_state_jellium(grid, n_particles, spinless, plane_wave=True) restricted_hamiltonian = jw_number_restrict_operator( hamiltonian_sparse, n_particles) E_g = get_ground_state(restricted_hamiltonian)[0] E_HF_plane_wave = expectation(hamiltonian_sparse, hf_state) self.assertAlmostEqual(E_g, E_HF_plane_wave, places=5)
def test_apply_constraints(self): # Get norm of original operator. original_norm = 0. for term, coefficient in self.fermion_hamiltonian.terms.items(): if term != (): original_norm += abs(coefficient) # Get modified operator. modified_operator = apply_constraints(self.fermion_hamiltonian, self.n_fermions) modified_operator.compress() # Get norm of modified operator. modified_norm = 0. for term, coefficient in modified_operator.terms.items(): if term != (): modified_norm += abs(coefficient) self.assertTrue(modified_norm < original_norm) # Map both to sparse matrix under Jordan-Wigner. sparse_original = get_sparse_operator(self.fermion_hamiltonian) sparse_modified = get_sparse_operator(modified_operator) # Check spectra. sparse_original = jw_number_restrict_operator(sparse_original, self.n_fermions) sparse_modified = jw_number_restrict_operator(sparse_modified, self.n_fermions) original_spectrum = sparse_eigenspectrum(sparse_original) modified_spectrum = sparse_eigenspectrum(sparse_modified) spectral_deviation = numpy.amax( numpy.absolute(original_spectrum - modified_spectrum)) self.assertAlmostEqual(spectral_deviation, 0.) # Check expectation value. energy, wavefunction = get_ground_state(sparse_original) modified_energy = expectation(sparse_modified, wavefunction) self.assertAlmostEqual(modified_energy, energy) # Test consistency with cvxopt. scipy_operator = apply_constraints(self.fermion_hamiltonian, self.n_fermions, use_scipy=False) # Get norm. scipy_norm = 0. for term, coefficient in scipy_operator.terms.items(): if term != (): scipy_norm += abs(coefficient) self.assertAlmostEqual(scipy_norm, modified_norm)
def test_hf_state_energy_same_in_plane_wave_and_dual_basis(self): grid_length = 4 dimension = 1 wigner_seitz_radius = 10.0 spinless = False n_orbitals = grid_length**dimension if not spinless: n_orbitals *= 2 n_particles = n_orbitals // 2 length_scale = wigner_seitz_length_scale(wigner_seitz_radius, n_particles, dimension) grid = Grid(dimension, grid_length, length_scale) hamiltonian = jellium_model(grid, spinless) hamiltonian_dual_basis = jellium_model(grid, spinless, plane_wave=False) # Get the Hamiltonians as sparse operators. hamiltonian_sparse = get_sparse_operator(hamiltonian) hamiltonian_dual_sparse = get_sparse_operator(hamiltonian_dual_basis) hf_state = hartree_fock_state_jellium(grid, n_particles, spinless, plane_wave=True) hf_state_dual = hartree_fock_state_jellium(grid, n_particles, spinless, plane_wave=False) E_HF_plane_wave = expectation(hamiltonian_sparse, hf_state) E_HF_dual = expectation(hamiltonian_dual_sparse, hf_state_dual) self.assertAlmostEqual(E_HF_dual, E_HF_plane_wave)
def compute_energy_expectation(bond_length, state): """Computes the energy expectation given the bond length (to fetch the Hamiltonian) and state. Args: ===== bond_length : float Bond length for fetching correct Hamiltonian state : numpy.ndarray State vector Returns: ======== energy : float Energy expectation value """ energy = expectation(sparse_hamiltonians[dist_list.index(bond_length)], state) return np.real(energy)
def test_constraint_matrix(self): # Randomly project operator with constraints. numpy.random.seed(8) constraints = constraint_matrix(self.n_orbitals, self.n_fermions) n_constraints, n_terms = constraints.shape self.assertEqual(1 + self.n_orbitals**2 + self.n_orbitals**4, n_terms) random_weights = numpy.random.randn(n_constraints) vectorized_operator = operator_to_vector(self.fermion_hamiltonian) modification_vector = constraints.transpose() * random_weights new_operator_vector = vectorized_operator + modification_vector modified_operator = vector_to_operator(new_operator_vector, self.n_orbitals) # Map both to sparse matrix under Jordan-Wigner. sparse_original = get_sparse_operator(self.fermion_hamiltonian) sparse_modified = get_sparse_operator(modified_operator) # Check expectation value. energy, wavefunction = get_ground_state(sparse_original) modified_energy = expectation(sparse_modified, wavefunction) self.assertAlmostEqual(modified_energy, energy)
def objective_function(self, amps=None): """ This function returns the Hamiltonian expectation value over the final circuit output state. If argument packed_amps is given, the circuit will run with those parameters. Otherwise, the initial angles will be used. :param [list(), numpy.ndarray] amps: list of circuit angles to run the objective function over. :return: energy estimate :rtype: float """ E = 0 t = time.time() if amps is None: packed_amps = self.initial_packed_amps elif isinstance(amps, np.ndarray): packed_amps = amps.tolist() elif isinstance(amps, list): packed_amps = amps else: raise TypeError('Please supply the circuit parameters' ' as a list or np.ndarray') if self.tomography: if (not self.parametric_way) and (self.strategy == 'UCCSD'): # modify hard-coded type ansatz circuit based # on packed_amps angles self.ansatz = uccsd_ansatz_circuit( packed_amps, self.molecule.n_orbitals, self.molecule.n_electrons, cq=self.custom_qubits) if self.experiment_list is None or not self.parametric_way: self.compile_tomo_expts() self.run_experiments(packed_amps) for term in self.pauli_list: key = term.operations_as_set() if len(key) > 0: E += term.coefficient*self.term_es[key] else: E += term.coefficient elif self.method == 'WFS': # In the direct WFS method without tomography, # direct access to wavefunction is allowed and expectation # value is exact each run. if self.parametric_way: E += WavefunctionSimulator().expectation( self.ref_state+self.ansatz, self.pauli_sum, {'theta': packed_amps}).real # attach parametric angles here else: if packed_amps is not None: # modify hard-coded type ansatz circuit # based on packed_amps angles self.ansatz = uccsd_ansatz_circuit( packed_amps, self.molecule.n_orbitals, self.molecule.n_electrons, cq=self.custom_qubits) E += WavefunctionSimulator().expectation( self.ref_state+self.ansatz, self.pauli_sum).real elif self.method == 'Numpy': if self.parametric_way: raise ValueError('NumpyWavefunctionSimulator() backend' ' does not yet support parametric programs.') else: if packed_amps is not None: self.ansatz = uccsd_ansatz_circuit( packed_amps, self.molecule.n_orbitals, self.molecule.n_electrons, cq=self.custom_qubits) E += NumpyWavefunctionSimulator(n_qubits=self.n_qubits).\ do_program(self.ref_state+self.ansatz).\ expectation(self.pauli_sum).real elif self.method == 'linalg': # check if molecule has data sufficient to construct UCCSD ansatz # and propagate starting from HF state if self.molecule is not None: propagator = normal_ordered( uccsd_singlet_generator( packed_amps, 2 * self.molecule.n_orbitals, self.molecule.n_electrons, anti_hermitian=True)) qubit_propagator_matrix = get_sparse_operator( propagator, n_qubits=self.n_qubits) uccsd_state = expm_multiply(qubit_propagator_matrix, self.initial_psi) expected_uccsd_energy = expectation( self.hamiltonian_matrix, uccsd_state).real E += expected_uccsd_energy else: # apparently no molecule was supplied; # attempt to just propagate the ansatz from user-specified # initial state, using a circuit unitary # if supplied by the user, otherwise the initial state itself, # and then estimate over <H> if self.initial_psi is None: raise ValueError('Warning: no initial wavefunction set.' ' Please set using ' 'VQEexperiment().set_initial_state()') # attempt to propagate with a circuit unitary if self.circuit_unitary is None: psi = self.initial_psi else: psi = expm_multiply(self.circuit_unitary, self.initial_psi) E += expectation(self.hamiltonian_matrix, psi).real else: raise ValueError('Impossible method: please choose from method' ' = {WFS, Numpy, linalg} if Tomography is set' ' to False, or choose from method = ' '{QC, WFS, Numpy, linalg} if tomography is set to True') E = E.real if self.verbose: self.it_num += 1 print('black-box function call #' + str(self.it_num)) print('Energy estimate is now: ' + str(E)) print('at angles: ', packed_amps) print('and this took ' + '{0:.3f}'.format(time.time()-t) + \ ' seconds to evaluate') self.history.append(E) return E
def test_ucc_h2_singlet(self): geometry = [('H', (0., 0., 0.)), ('H', (0., 0., 0.7414))] basis = 'sto-3g' multiplicity = 1 filename = os.path.join(THIS_DIRECTORY, 'data', 'H2_sto-3g_singlet_0.7414') self.molecule = MolecularData(geometry, basis, multiplicity, filename=filename) self.molecule.load() # Get molecular Hamiltonian. self.molecular_hamiltonian = self.molecule.get_molecular_hamiltonian() # Get FCI RDM. self.fci_rdm = self.molecule.get_molecular_rdm(use_fci=1) # Get explicit coefficients. self.nuclear_repulsion = self.molecular_hamiltonian.constant self.one_body = self.molecular_hamiltonian.one_body_tensor self.two_body = self.molecular_hamiltonian.two_body_tensor # Get fermion Hamiltonian. self.fermion_hamiltonian = normal_ordered( get_fermion_operator(self.molecular_hamiltonian)) # Get qubit Hamiltonian. self.qubit_hamiltonian = jordan_wigner(self.fermion_hamiltonian) # Get the sparse matrix. self.hamiltonian_matrix = get_sparse_operator( self.molecular_hamiltonian) # Test UCCSD for accuracy against FCI using loaded t amplitudes. ucc_operator = uccsd_generator(self.molecule.ccsd_single_amps, self.molecule.ccsd_double_amps) hf_state = jw_hartree_fock_state(self.molecule.n_electrons, count_qubits(self.qubit_hamiltonian)) uccsd_sparse = jordan_wigner_sparse(ucc_operator) uccsd_state = scipy.sparse.linalg.expm_multiply(uccsd_sparse, hf_state) expected_uccsd_energy = expectation(self.hamiltonian_matrix, uccsd_state) self.assertAlmostEqual(expected_uccsd_energy, self.molecule.fci_energy, places=4) print("UCCSD ENERGY: {}".format(expected_uccsd_energy)) # Test CCSD singlet for precise match against FCI using loaded t # amplitudes packed_amplitudes = uccsd_singlet_get_packed_amplitudes( self.molecule.ccsd_single_amps, self.molecule.ccsd_double_amps, self.molecule.n_qubits, self.molecule.n_electrons) ccsd_operator = uccsd_singlet_generator(packed_amplitudes, self.molecule.n_qubits, self.molecule.n_electrons, anti_hermitian=False) ccsd_sparse_r = jordan_wigner_sparse(ccsd_operator) ccsd_sparse_l = jordan_wigner_sparse( -hermitian_conjugated(ccsd_operator)) ccsd_state_r = scipy.sparse.linalg.expm_multiply( ccsd_sparse_r, hf_state) ccsd_state_l = scipy.sparse.linalg.expm_multiply( ccsd_sparse_l, hf_state) expected_ccsd_energy = ccsd_state_l.conjugate().dot( self.hamiltonian_matrix.dot(ccsd_state_r)) self.assertAlmostEqual(expected_ccsd_energy, self.molecule.fci_energy)
def expectation_value(operator, state): f = filter_terms(normal_ordered(operator)) return expectation(get_sparse_operator(f, n_qubits=active_spin_orbitals), state) if len(f.terms) else 0j