def uccsd_singlet_evolution(packed_amplitudes,
                            n_qubits,
                            n_electrons,
                            fermion_transform=jordan_wigner):
    """Create a ProjectQ evolution operator for a UCCSD singlet circuit

    Args:
        packed_amplitudes(ndarray): Compact array storing the unique single
            and double excitation amplitudes for a singlet UCCSD operator.
            The ordering lists unique single excitations before double
            excitations.
        n_qubits(int): Number of spin-orbitals used to represent the system,
            which also corresponds to number of qubits in a non-compact map.
        n_electrons(int): Number of electrons in the physical system
        fermion_transform(openfermion.transform): The transformation that
            defines the mapping from Fermions to QubitOperator.

    Returns:
        evoution_operator(TimeEvolution): The unitary operator
            that constructs the UCCSD singlet state.
    """
    # Build UCCSD generator
    fermion_generator = uccsd_singlet_generator(packed_amplitudes, n_qubits,
                                                n_electrons)

    evolution_operator = uccsd_evolution(fermion_generator, fermion_transform)

    return evolution_operator
示例#2
0
def build_singlet_uccsd_circuit(parameters, n_mo, n_electrons, transformation):
    """Constructs the circuit for a singlet UCCSD ansatz with HF initial state.

    Args:
        parameters (numpy.ndarray): Vector of coupled-cluster amplitudes
        n_mo (int): number of molecular orbitals
        n_electrons (int): number of electrons

    Returns:
        qprogram (pyquil.quil.Program): a program for simulating the ansatz
    """
    qprogram = Program()

    # Set initial state with correct number of electrons
    for i in range(n_electrons):
        qubit_index = n_electrons-i-1
        qprogram += X(qubit_index)

    # Build UCCSD generator
    fermion_generator = uccsd_singlet_generator(parameters,
                                                2*n_mo,
                                                n_electrons,
                                                anti_hermitian=True)
    evolution_operator = exponentiate_fermion_operator(fermion_generator,
                                                       transformation)

    qprogram += evolution_operator
    return Circuit(qprogram)
示例#3
0
def uccsd_ansatz_circuit(packed_amplitudes, n_orbitals, n_electrons, cq=None):
    # TODO apply logical re-ordering to Fermionic non-parametric way too!
    """
    This function returns a UCCSD variational ansatz with hard-coded gate angles. The number of orbitals specifies the
    number of qubits, the number of electrons specifies the initial HF reference state which is assumed was prepared.
    The packed_amplitudes input defines which gate angles to apply for each CC operation. The list cq is an optional
    list which specifies the qubit label ordering which is to be assumed.

    :param list() packed_amplitudes: amplitudes t_ij and t_ijkl of the T_1 and T_2 operators of the UCCSD ansatz
    :param int n_orbitals: number of *spatial* orbitals
    :param int n_electrons: number of electrons considered
    :param list() cq: list of qubit label order

    :return: circuit which prepares the UCCSD variational ansatz
    :rtype: Program
    """

    # Fermionic UCCSD
    uccsd_propagator = normal_ordered(
        uccsd_singlet_generator(packed_amplitudes, 2 * n_orbitals,
                                n_electrons))
    qubit_operator = jordan_wigner(uccsd_propagator)

    # convert the fermionic propagator to a Pauli spin basis via JW, then convert to a Pyquil compatible PauliSum
    pyquilpauli = qubitop_to_pyquilpauli(qubit_operator)

    # re-order logical stuff if necessary
    if cq is not None:
        pauli_list = [PauliTerm("I", 0, 0.0)]
        for term in pyquilpauli.terms:
            new_term = term
            # if the QPU has a custom lattice labeling, supplied by user through a list cq, reorder the Pauli labels.
            if cq is not None:
                new_term = term.coefficient
                for pauli in term:
                    new_index = cq[pauli[0]]
                    op = pauli[1]
                    new_term = new_term * PauliTerm(op=op, index=new_index)

            pauli_list.append(new_term)
        pyquilpauli = PauliSum(pauli_list)

    # create a pyquil program which performs the ansatz state preparation with angles unpacked from packed_amplitudes
    ansatz_prog = Program()

    # add each term as successive exponentials (1 single Trotter step, not taking into account commutation relations!)
    for commuting_set in commuting_sets(simplify_pauli_sum(pyquilpauli)):
        ansatz_prog += exponentiate_commuting_pauli_sum(
            -1j * PauliSum(commuting_set))(-1.0)

    return ansatz_prog
示例#4
0
    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
示例#5
0
    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