def exp_hamiltoniantrot_H2(time, atomic_distance, trotter_order): """ Here we are using some packages related to quantum chemistry to obtain hamiltonian of H2 molecule. The we are translating this hamiltonian into sequence of pyquil gates. And finally, we are trotterazing the exponent of the hamiltonian (exp(-iHt)) of our system and returning the obtained pyquil program :param time: is t in the exp(iHt). Note the + sign. :param atomic_distance: the distance between to H atoms in H2 molecule :return: program of the Trotter-Suzuki decomposition of hamiltonian exp(iHt) for H2 molecule """ geometry = [['H', [0, 0, 0]], ['H', [0, 0, atomic_distance]]] # H--H distance = 0.74angstrom basis = 'sto-3g' multiplicity = 1 # (2S+1) charge = 0 h2_molecule = MolecularData(geometry, basis, multiplicity, charge) h2_molecule = run_pyscf(h2_molecule) h2_qubit_hamiltonian = jordan_wigner(get_fermion_operator(h2_molecule.get_molecular_hamiltonian())) pyquil_h2_qubit_hamiltonian = qubitop_to_pyquilpauli(h2_qubit_hamiltonian) return trotterization(pyquil_h2_qubit_hamiltonian, float(time), trotter_order)
def one_particle(H: sparse.coo_matrix) -> pyquil.paulis.PauliSum: """ Generates a PauliSum(pyquil) given a Hamiltonian-matrix. Creates a Hamiltonian operator from a (sparse) matrix. This function uses a one-particle formulation and, thus, requires N qubits for an N-dimensional Hilbert space. @author: Axel, Joel :param H: An array (array_like) representing the hamiltonian. :return: Hamiltonian as PyQuil PauliSum. """ # Create the Hamiltonian with a and a^dagger Hamiltonian = FermionOperator() if sparse.issparse(H): H = H.tocoo() for i, j, data in zip(H.row, H.col, H.data): Hamiltonian += data * FermionOperator(((int(i), 1), (int(j), 0))) else: if not isinstance(H, np.ndarray): H = np.asanyarray(H) for i in range(H.shape[0]): for j in range(H.shape[1]): Hamiltonian += H[i, j] * FermionOperator(((i, 1), (j, 0))) Hamiltonian = jordan_wigner(Hamiltonian) Hamiltonian = qubitop_to_pyquilpauli(Hamiltonian) return Hamiltonian
def get_ground_energy(interaction_hamil, numLayer): fermionop_hamil = FermionOperator() for key in interaction_hamil: value = interaction_hamil[key] fermionop_hamil += FermionOperator(term=key, coefficient=value) qubitop_hamil = bravyi_kitaev(fermionop_hamil) pauliop_hamil = qubitop_to_pyquilpauli(qubitop_hamil) return solve_vqe(pauliop_hamil, numLayer)
def get_exact_expectation_values(self, circuit, qubit_operator, **kwargs): if self.device_name != 'wavefunction-simulator' and self.n_samples!=None: raise Exception("Exact expectation values work only for the wavefunction simulator and n_samples equal to None.") cxn = get_forest_connection(self.device_name) # Pyquil does not support PauliSums with no terms. if len(qubit_operator.terms) == 0: return ExpectationValues(np.zeros((0,))) pauli_sum = qubitop_to_pyquilpauli(qubit_operator) expectation_values = cxn.expectation(circuit.to_pyquil(), pauli_sum.terms) if expectation_values.shape[0] != len(pauli_sum): raise(RuntimeError("Expected {} expectation values but received {}.".format(len(pauli_sum), expectation_values.shape[0]))) return ExpectationValues(expectation_values)
def build_qaoa_circuit(params, hamiltonians): """Generates a circuit for QAOA. This is not only for QAOA proposed by Farhi et al., but also general ansatz where alternating layers of time evolution under two different Hamiltonians H1 and H2 are considered. Args: hamiltonians (list): A list of dict or zquantum.core.qubitoperator.QubitOperator objects representing Hamiltonians H1, H2, ..., Hk which forms one layer of the ansatz exp(-i Hk tk) ... exp(-i H2 t2) exp(-i H1 t1) For example, in the case of QAOA proposed by Farhi et al, the list the list is then [H1, H2] where H1 is the Hamiltonian for which the ground state is sought, and H2 is the Hamiltonian for which the time evolution act as a diffuser in the search space. params (numpy.ndarray): A list of sets of parameters. Each parameter in a set specifies the time duration of evolution under each of the Hamiltonians H1, H2, ... Hk. Returns: zquantum.core.circuit.Circuit: the ansatz circuit """ if mod(len(params), len(hamiltonians)) != 0: raise Warning('There are {} input parameters and {} Hamiltonians. Since {} does not divide {} the last layer will be incomplete.'.\ format(len(params), len(hamiltonians), len(params), len(hamiltonians))) # Convert qubit operators from dicts to QubitOperator objects, if needed for index, hamiltonian in enumerate(hamiltonians): if isinstance(hamiltonian, dict): hamiltonians[index] = convert_dict_to_qubitop(hamiltonian) output = Circuit() # Start with a layer of Hadarmard gates n_qubits = count_qubits(hamiltonians[0]) qubits = [Qubit(qubit_index) for qubit_index in range(n_qubits)] output.qubits = qubits for qubit_index in range(n_qubits): output.gates.append(Gate('H', (qubits[qubit_index], ))) # Add time evolution layers for i in range(params.shape[0]): hamiltonian_index = mod(i, len(hamiltonians)) current_hamiltonian = qubitop_to_pyquilpauli( hamiltonians[hamiltonian_index]) output += time_evolution(current_hamiltonian, params[i]) return output
def multi_particle(H: sparse.coo_matrix) -> pyquil.paulis.PauliSum: """ Creates a Qubit-operator from a (sparse) matrix. This function uses (almost) all states and, thus, requires (approximately) log(N) qubits for an N-dimensional Hilbert space. The idea for converting a matrix element to an operator is to raise/lower the qubits differing between two states to convert one basis-state to the other. The qubits that are not negated must be checked to have the correct value (using an analogue of the counting operator) to not get extra terms (one could perhaps allow for extra terms and compensate for them later?). 0 = up 1 = down (1+Zi)/2 checks that qubit i is 0 (up) (1-Zi)/2 checks that qubit i is 1 (down) (Xi+1j*Yi)/2 lowers qubit from 1 (down) to 0 (up) (Xi-1j*Yi)/2 raises qubit from 0 (up) to 1 (down) @author: Joel :param H: An array (array-like) representing the hamiltonian. :return: Hamiltonian as PyQuil PauliSum. """ # Convert to sparse coo_matrix if not sparse.issparse(H): H = sparse.coo_matrix(H) elif H.getformat() != "coo": H = H.tocoo() # The main part of the function H_op = QubitOperator() for i, j, data in zip(H.row, H.col, H.data): new_term = QubitOperator(()) # = I for qubit in range(int.bit_length(H.shape[0] - 1)): if (i ^ j) & (1 << qubit): # lower/raise qubit new_term *= QubitOperator((qubit, "X"), 1 / 2) + \ QubitOperator((qubit, "Y"), 1j * (int( j & (1 << qubit) != 0) - 1 / 2)) else: # check that qubit has correct value (same as i and j) new_term *= QubitOperator((), 1 / 2) + \ QubitOperator((qubit, "Z"), 1 / 2 - int(j & (1 << qubit) != 0)) H_op += data * new_term return qubitop_to_pyquilpauli(H_op)
def one_particle_ucc(h, reference=1, trotter_order=1, trotter_steps=1): """ UCC-style ansatz preserving particle number. @author: Joel, Carl :param np.ndarray h: The hamiltonian matrix. :param int reference: the binary number corresponding to the reference state (which must be a Fock-state). :param int trotter_order: trotter order in suzuki_trotter :param int trotter_steps: trotter steps in suzuki_trotter :return: function(theta) which returns the ansatz Program """ dim = h.shape[0] terms = [] for occupied in range(dim): if reference & (1 << occupied): for unoccupied in range(dim): if not reference & (1 << unoccupied): term = FermionOperator(((unoccupied, 1), (occupied, 0))) \ - FermionOperator(((occupied, 1), (unoccupied, 0))) terms.append(qubitop_to_pyquilpauli(jordan_wigner(term))) exp_maps = trotterize(terms, trotter_order, trotter_steps) def wrap(theta): """ Returns the ansatz Program. :param np.ndarray theta: parameters :return: the Program :rtype: pyquil.Program """ prog = Program() for qubit in range(int.bit_length(reference)): if reference & (1 << qubit): prog += X(qubit) for idx, exp_map in enumerate(exp_maps): for exp in exp_map: prog += exp(theta[idx]) return prog return wrap
def expectation(p1, p2, p3, multi=1): """ Return UCC expectation value for a specified geometry * First runs a psi4 ccsd calculation to get single and double amplitudes to use as ansatz for UCC * Generates a Hamiltonian for the specified geometry * Obtains expectation value using VQE """ geometry = [['O', p1], ['H', p2], ['H', p3]] molecule = MolecularData(geometry, basis='sto-3g', multiplicity=multi, description=str(round(rad, 2)) + "_" + str(round(ang, 2))) # Run Psi4. molecule = run_psi4(molecule, run_ccsd=1, run_fci=1) # Print out some results of calculation. print('\nRAD: {}, ANG: {}\n'.format(rad, ang)) print('FCI energy: {} Hartree.'.format(molecule.fci_energy)) singles_initial = molecule.ccsd_single_amps.flatten() doubles_initial = molecule.ccsd_double_amps.flatten() amps = np.concatenate((singles_initial, doubles_initial), axis=0) print("Compiling the Hamiltonian...") hamiltonian = jordan_wigner( get_fermion_operator(molecule.get_molecular_hamiltonian())) hamiltonian.compress() hamiltonian = qubitop_to_pyquilpauli(hamiltonian) print("Hamiltonian complete") vqe = VQE(minimizer=minimize, minimizer_kwargs={ 'method': 'nelder-mead', 'options': { 'fatol': 1.5e-3 } }) result = vqe.expectation(ansatz(amps), hamiltonian, None, qvm) print("VQE Expectation Value: {} Hartree".format(result)) return result
def get_molecule_openfermion(molecule, eigen_index=0): # check if the molecule is in the molecules data directory if molecule.name + '.hdf5' in os.listdir(DATA_DIRECTORY): print("\tLoading File from {}".format(DATA_DIRECTORY)) molecule.load() else: # compute properties with run_psi4 molecule = run_psi4(molecule, run_fci=True) print("\tPsi4 Calculation Completed") print("\tSaved in {}".format(DATA_DIRECTORY)) molecule.save() fermion_hamiltonian = molecule.get_molecular_hamiltonian() qubitop_hamiltonian = jordan_wigner(fermion_hamiltonian) psum = qubitop_to_pyquilpauli(qubitop_hamiltonian) ham = tensor_up(psum, molecule.n_qubits) if isinstance(ham, (csc_matrix, csr_matrix)): ham = ham.toarray() w, v = np.linalg.eigh(ham) gs_wf = v[:, [eigen_index]] n_density = gs_wf.dot(np.conj(gs_wf).T) return molecule, v[:, [eigen_index]], n_density, w[eigen_index]
from openfermion.utils import hermitian_conjugated from forestopenfermion import qubitop_to_pyquilpauli, pyquilpauli_to_qubitop from pyquil.quil import Program from pyquil.gates import X from pyquil.paulis import exponentiate from pyquil.api import QVMConnection hubbard_hamiltonian = FermionOperator() spatial_orbitals = 4 for i in range(spatial_orbitals): electron_hop_alpha = FermionOperator(((2*i, 1), (2*((i+1) % spatial_orbitals), 0))) electron_hop_beta = FermionOperator(((2*i+1, 1), ((2 * ((i+1) % spatial_orbitals) + 1), 0))) hubbard_hamiltonian += -1 * (electron_hop_alpha + hermitian_conjugated(electron_hop_alpha)) hubbard_hamiltonian += -1 * (electron_hop_beta + hermitian_conjugated(electron_hop_beta)) hubbard_hamiltonian += FermionOperator(((2*i, 1), (2*i, 0), (2*i+1, 1), (2*i+1, 0)), 4.0) hubbard_term_generator = jordan_wigner(hubbard_hamiltonian) pyquil_hubbard_generator = qubitop_to_pyquilpauli(hubbard_term_generator) localized_electrons_program = Program() localized_electrons_program.inst([X(0), X(1)]) pyquil_program = Program() for term in pyquil_hubbard_generator.terms: pyquil_program += exponentiate(0.1*term) print (localized_electrons_program + pyquil_program) qvm = QVMConnection() print(qvm.run(pyquil_program, [0], 10)) wf = qvm.wavefunction(pyquil_program) print(wf)
# Generate and populate instance of MolecularData. molecule = MolecularData(geometry, basis, spin, description="h3") molecule = run_pyscf(molecule, run_scf=run_scf, run_mp2=run_mp2, run_cisd=run_cisd, run_ccsd=run_ccsd, run_fci=run_fci) # Use a Jordan-Wigner encoding, and compress to remove 0 imaginary components qubit_hamiltonian = jordan_wigner(molecule.get_molecular_hamiltonian()) qubit_hamiltonian.compress() pauli_hamiltonian = qubitop_to_pyquilpauli(qubit_hamiltonian) vqe_inst = VQE(minimizer=minimize, minimizer_kwargs={'method': 'nelder-mead'}) # angle = 2.0 # result = vqe_inst.expectation(small_ansatz([angle]), pauli_hamiltonian, None, qvm) # print(result) angle_range = np.linspace(0.0, 2 * np.pi, 20) data = [ vqe_inst.expectation(small_ansatz([angle]), pauli_hamiltonian, None, qvm) for angle in angle_range ] import matplotlib.pyplot as plt plt.xlabel('Angle [radians]')
# generate the spin-adapted classical coupled-cluster amplitude to use as the input for the # circuit packed_amps = uccsd_singlet_get_packed_amplitudes( molecule.ccsd_single_amps, molecule.ccsd_double_amps, molecule.n_qubits, molecule.n_electrons, ) theta = packed_amps[-1] # always take the doubles amplitude # now that we're done setting up the Hamiltonian and grabbing initial opt parameters # we can switch over to how to run things ucc_program = ucc_circuit(theta) paulis_bk_hamiltonian = qubitop_to_pyquilpauli(bk_hamiltonian) bk_mat = tensor_up(paulis_bk_hamiltonian, [0, 1]) w, v = np.linalg.eigh(bk_mat) wf = qvm.wavefunction(ucc_program) wf = wf.amplitudes.reshape((-1, 1)) tenergy = np.conj(wf).T.dot(bk_mat).dot(wf)[0, 0].real # observable = objective_fun(theta, hamiltonian=paulis_bk_hamiltonian, quantum_resource=qvm) observable = objective_fun(theta, hamiltonian=bk_mat, quantum_resource=qvm) result = minimize( objective_fun, x0=theta, args=(bk_mat, qvm), method="CG", options={"disp": True} )
def __init__(self, ansatz, molecule, mean_field=None, backend_options={"backend": "wavefunction_simulator"}): """Initialize the settings for simulation. If the mean field is not provided it is automatically calculated. Args: ansatz (OpenFermionParametricSolver.Ansatze): Ansatz for the quantum solver. molecule (pyscf.gto.Mole): The molecule to simulate. mean_field (pyscf.scf.RHF): The mean field of the molecule. """ # Check the ansatz assert (isinstance(ansatz, RigettiParametricSolver.Ansatze)) self.ansatz = ansatz # Calculate the mean field if the user has not already done it. if not mean_field: mean_field = scf.RHF(molecule) mean_field.verbose = 0 mean_field.scf() if not mean_field.converged: orb_temp = mean_field.mo_coeff occ_temp = mean_field.mo_occ nr = scf.newton(mean_field) energy = nr.kernel(orb_temp, occ_temp) mean_field = nr # Check the convergence of the mean field if not mean_field.converged: warnings.warn( "RigettiParametricSolver simulating with mean field not converged.", RuntimeWarning) self.molecule = molecule self.mean_field = mean_field # Initialize the amplitudes (parameters to be optimized) self.optimized_amplitudes = [] # Set the parameters for Openfermion self.of_mole = self._build_of_molecule(molecule, mean_field) # Set the fermionic Hamiltonian self.f_hamiltonian = self.of_mole.get_molecular_hamiltonian() # Transform the fermionic Hamiltonian into qubit Hamiltonian self.qubit_hamiltonian = jordan_wigner(self.f_hamiltonian) self.qubit_hamiltonian.compress() # Also stores the Rigetti/Forest qubit Hamiltonian self.forest_qubit_hamiltonian = qubitop_to_pyquilpauli( self.qubit_hamiltonian) # Set the dimension of the amplitudes if ansatz == RigettiParametricSolver.Ansatze.UCCSD: no_occupied = int(np.ceil(molecule.nelectron / 2)) no_virtual = len(mean_field.mo_energy) - no_occupied no_t1 = no_occupied * no_virtual no_t2 = no_t1 * (no_t1 + 1) / 2 self.amplitude_dimension = int(no_t1 + no_t2) # Set the number of qubits self.n_qubits = self.of_mole.n_qubits # Instantiate backend for computation self.backend_options = dict() if ("backend" in backend_options) and (backend_options["backend"] != "wavefunction_simulator"): self.backend_options["backend"] = get_qc( backend_options["backend"]) else: self.backend_options["backend"] = WavefunctionSimulator() self.backend_options["n_shots"] = backend_options["n_shots"] if ( "n_shots" in backend_options) else 1000
def get_rdm(self): """Obtain the RDMs from the optimized amplitudes. Obtain the RDMs from the optimized amplitudes by using the same function for energy evaluation. The RDMs are computed by using each fermionic Hamiltonian term, transforming them and computing the elements one-by-one. Note that the Hamiltonian coefficients will not be multiplied as in the energy evaluation. The first element of the Hamiltonian is the nuclear repulsion energy term, not the Hamiltonian term. Returns: (numpy.array, numpy.array): One & two-particle RDMs (rdm1_np & rdm2_np, float64). Raises: RuntimeError: If no simulation has been run. """ if len(self.optimized_amplitudes) == 0: raise RuntimeError( "Cannot retrieve RDM because no simulation has been run.") # Save our accurate hamiltonian tmp_hamiltonian = self.qubit_hamiltonian # Initialize the RDM arrays rdm1_np = np.zeros((self.of_mole.n_orbitals, ) * 2) rdm2_np = np.zeros((self.of_mole.n_orbitals, ) * 4) # Loop over each element of Hamiltonian (non-zero value) for ikey, key in enumerate(self.f_hamiltonian): length = len(key) # Treat one-body and two-body term accordingly if (length == 2): pele, qele = int(key[0][0]), int(key[1][0]) iele, jele = pele // 2, qele // 2 if (length == 4): pele, qele, rele, sele = int(key[0][0]), int(key[1][0]), int( key[2][0]), int(key[3][0]) iele, jele, kele, lele = pele // 2, qele // 2, rele // 2, sele // 2 # Select the Hamiltonian element (Set coefficient to one) hamiltonian_temp = self.of_mole.get_molecular_hamiltonian() for jkey, key2 in enumerate(hamiltonian_temp): hamiltonian_temp[key2] = 1. if (key == key2 and ikey != 0) else 0. # Qubitize the element qubit_hamiltonian2 = jordan_wigner(hamiltonian_temp) qubit_hamiltonian2.compress() # Overwrite with the temp hamiltonian self.qubit_hamiltonian = qubit_hamiltonian2 self.forest_qubit_hamiltonian = qubitop_to_pyquilpauli( self.qubit_hamiltonian) # Calculate the energy with the temp hamiltonian opt_energy2 = self.simulate(self.optimized_amplitudes) # Put the values in np arrays (differentiate 1- and 2-RDM) if (length == 2): rdm1_np[iele, jele] = rdm1_np[iele, jele] + opt_energy2 elif (length == 4): if ((iele != lele) or (jele != kele)): rdm2_np[lele, iele, kele, jele] = rdm2_np[lele, iele, kele, jele] + 0.5 * opt_energy2 rdm2_np[iele, lele, jele, kele] = rdm2_np[iele, lele, jele, kele] + 0.5 * opt_energy2 else: rdm2_np[iele, lele, jele, kele] = rdm2_np[iele, lele, jele, kele] + opt_energy2 # Restore the accurate hamiltonian self.qubit_hamiltonian = tmp_hamiltonian return rdm1_np, rdm2_np
def simulate(self, amplitudes): """Perform the simulation for the molecule. Args: amplitudes (list): The initial amplitudes (float64). Returns: float64: The total energy (energy). Raise: ValueError: If the dimension of the amplitude list is incorrect. """ if len(amplitudes) != self.amplitude_dimension: raise ValueError("Incorrect dimension for amplitude list.") #Anti-hermitian operator and its qubit form generator = uccsd_singlet_generator(amplitudes, self.of_mole.n_qubits, self.of_mole.n_electrons) jw_generator = jordan_wigner(generator) pyquil_generator = qubitop_to_pyquilpauli(jw_generator) p = Program(Pragma('INITIAL_REWIRING', ['"GREEDY"'])) # Set initial wavefunction (Hartree-Fock) for i in range(self.of_mole.n_electrons): p.inst(X(i)) # Trotterization (unitary for UCCSD state preparation) for term in pyquil_generator.terms: term.coefficient = np.imag(term.coefficient) p += exponentiate(term) p.wrap_in_numshots_loop(self.backend_options["n_shots"]) # Do not simulate if no operator was passed if len(self.qubit_hamiltonian.terms) == 0: return 0. else: # Run computation using the right backend if isinstance(self.backend_options["backend"], WavefunctionSimulator): energy = self.backend_options["backend"].expectation( prep_prog=p, pauli_terms=self.forest_qubit_hamiltonian) else: # Set up experiment, each setting corresponds to a particular measurement basis settings = [ ExperimentSetting(in_state=TensorProductState(), out_operator=forest_term) for forest_term in self.forest_qubit_hamiltonian.terms ] experiment = TomographyExperiment(settings=settings, program=p) print(experiment, "\n") results = self.backend_options["backend"].experiment( experiment) energy = 0. coefficients = [ forest_term.coefficient for forest_term in self.forest_qubit_hamiltonian.terms ] for i in range(len(results)): energy += results[i].expectation * coefficients[i] energy = np.real(energy) # Save the amplitudes so we have the optimal ones for RDM calculation self.optimized_amplitudes = amplitudes return energy
def build_qaoa_circuit_grads(params, hamiltonians): """ Generates gradient circuits and corresponding factors for the QAOA ansatz defined in the function build_qaoa_circuit. Args: hamiltonians (list): A list of dict or zquantum.core.qubitoperator.QubitOperator objects representing Hamiltonians H1, H2, ..., Hk which forms one layer of the ansatz exp(-i Hk tk) ... exp(-i H2 t2) exp(-i H1 t1) For example, in the case of QAOA proposed by Farhi et al, the list the list is then [H1, H2] where H1 is the Hamiltonian for which the ground state is sought, and H2 is the Hamiltonian for which the time evolution act as a diffuser in the search space. params (numpy.ndarray): A list of sets of parameters. Each parameter in a set specifies the time duration of evolution under each of the Hamiltonians H1, H2, ... Hk. Returns: gradient_circuits (list of lists of zquantum.core.circuit.Circuit: the circuits) circuit_factors (list of lists of floats): combination coefficients for the expectation values of the list of circuits. """ if mod(len(params), len(hamiltonians)) != 0: raise Warning('There are {} input parameters and {} Hamiltonians. Since {} does not divide {} the last layer will be incomplete.'.\ format(len(params), len(hamiltonians), len(params), len(hamiltonians))) # Convert qubit operators from dicts to QubitOperator objects, if needed for index, hamiltonian in enumerate(hamiltonians): if isinstance(hamiltonian, dict): hamiltonians[index] = convert_dict_to_qubitop(hamiltonian) hadamard_layer = Circuit() # Start with a layer of Hadarmard gates n_qubits = count_qubits(hamiltonians[0]) qubits = [Qubit(qubit_index) for qubit_index in range(n_qubits)] hadamard_layer.qubits = qubits for qubit_index in range(n_qubits): hadamard_layer.gates.append(Gate('H', (qubits[qubit_index], ))) # Add time evolution layers gradient_circuits = [] circuit_factors = [] for index1 in range(params.shape[0]): hamiltonian_index1 = mod(index1, len(hamiltonians)) current_hamiltonian = qubitop_to_pyquilpauli( hamiltonians[hamiltonian_index1]) derivative_circuits_for_index1, factors = time_evolution_derivatives( current_hamiltonian, params[index1]) param_circuits = [] for derivative_circuit in derivative_circuits_for_index1: output_circuit = Circuit() output_circuit.qubits = qubits output_circuit += hadamard_layer for index2 in range(params.shape[0]): hamiltonian_index2 = mod(index2, len(hamiltonians)) if index2 == index1: output_circuit += derivative_circuit else: current_hamiltonian = qubitop_to_pyquilpauli( hamiltonians[hamiltonian_index2]) output_circuit += time_evolution(current_hamiltonian, params[index2]) param_circuits.append(output_circuit) circuit_factors.append(factors) gradient_circuits.append(param_circuits) return gradient_circuits, circuit_factors
from example2 import molecule from openfermion.transforms import jordan_wigner, get_fermion_operator from forestopenfermion import qubitop_to_pyquilpauli from pyquil.quil import Program from pyquil.gates import X from pyquil.paulis import exponentiate from pyquil.api import QVMConnection from grove.pyvqe.vqe import VQE from scipy.optimize import minimize h2_qubit_hamiltonian = jordan_wigner( get_fermion_operator(molecule.get_molecular_hamiltonian())) pyquil_hamiltonian = qubitop_to_pyquilpauli(h2_qubit_hamiltonian) print(pyquil_hamiltonian) # # electrons_program = Program() # electrons_program.inst([X(0), X(1)]) pyquil_program = Program() for term in pyquil_hamiltonian.terms: pyquil_program += exponentiate(0.1 * term) print(pyquil_program) qvm = QVMConnection() wf = qvm.wavefunction(pyquil_program) print(wf) vqe_inst = VQE(minimizer=minimize, minimizer_kwargs={'method': 'nelder-mead'}) result = vqe_inst.expectation(pyquil_program, pyquil_hamiltonian, None, qvm) print(result)
def multi_particle_ucc(h, reference=0, trotter_order=1, trotter_steps=1): """ UCC-style ansatz that doesn't preserve anything (i.e. uses all basis states). This is basically an implementation of create_arbitrary_state (however, theta will not match the coefficients in the superposition) built on pyquil.paulis.exponentiate_map. One idea is to use lowering and raising operators and "check operators" instead of Xs. This results in a more straight-forward mapping of theta to the coefficients since no previously produced state will be mapped to other states in a later stage. To clarify: with the current implementation the state |1 1 0> will both be produced by exp(X2 X1) operating on |0 0 0> and by exp(X2) operating on exp(X1)|0 0 0> (which contains a |0 1 0> term). This could improve potential bad properties with the current implementation. However, it might be difficult to create commuting hermitian terms, which is required in exponential_map_commuting_pauli_terms. If this function is called multiple times, particularly if theta has the same length in all calls, caching terms might significantly increase performance. @author: Joel :param np.ndarray h: The hamiltonian matrix. :param reference: integer representation of reference state :param trotter_order: Trotter order in trotterization :param trotter_steps: Trotter steps in trotterization :return: function(theta) which returns the ansatz Program. -1j*theta[i] is the coefficient in front of the term prod_k X_k^bit(i,k) where bit(i, k) is the k'th bit of i in binary, in the exponent. """ dim = h.shape[0] terms = [] for state in range(dim): if state != reference: term = QubitOperator(()) for qubit in range(int.bit_length(state)): if (state ^ reference) & (1 << qubit): # lower/raise qubit term *= QubitOperator((qubit, "X"), 1 / 2) \ + QubitOperator((qubit, "Y"), 1j * (int( reference & (1 << qubit) != 0) - 1 / 2)) else: # check that qubit has correct value (same as i and j) term *= QubitOperator((), 1 / 2) \ + QubitOperator((qubit, "Z"), 1 / 2 - int( reference & (1 << qubit) != 0)) terms.append( qubitop_to_pyquilpauli(term - hermitian_conjugated(term))) exp_maps = trotterize(terms, trotter_order, trotter_steps) def wrap(theta): """ Returns the ansatz Program. :param np.ndarray theta: parameters :return: the Program :rtype: pyquil.Program """ prog = Program() for qubit in range(int.bit_length(reference)): if reference & (1 << qubit): prog += X(qubit) for idx, exp_map in enumerate(exp_maps): for exp in exp_map: prog += exp(theta[idx]) return prog return wrap