Exemple #1
0
    def augment_program_with_memory_values(quil_program, memory_map):
        p = Program()

        # we stupidly allowed memory_map to be of type Dict[MemoryReference, Any], whereas qc.run
        # takes a memory initialization argument of type Dict[str, List[Union[int, float]]. until
        # we are in a position to remove this, we support both styles of input.

        if len(memory_map.keys()) == 0:
            return quil_program
        elif isinstance(list(memory_map.keys())[0], MemoryReference):
            warn(
                "Use of memory_map values of type Dict[MemoryReference, Any] have been "
                "deprecated.  Please use Dict[str, List[Union[int, float]]], as with "
                "QuantumComputer.run .")
            for k, v in memory_map.items():
                p += MOVE(k, v)
        elif isinstance(list(memory_map.keys())[0], str):
            for name, arr in memory_map.items():
                for index, value in enumerate(arr):
                    p += MOVE(MemoryReference(name, offset=index), value)
        else:
            raise TypeError(
                "Bad memory_map type; expected Dict[str, List[Union[int, float]]]."
            )

        p += quil_program

        return percolate_declares(p)
Exemple #2
0
def augment_program_with_memory_values(quil_program, memory_map):
    """
    This function allocates the classical memory values (gate angles) to a parametric quil program in order to use it
    on a Numpy-based simulator

    :param Program quil_program: parametric quil program which would require classical memory allocation
    :param Dict memory_map: dictionary with as keys the MemoryReference or String descrbing the classical memory, and
        with items() an array of values for that classical memory

    :return: quil program with gate angles from memory_map allocated to the (originally parametric) program
    :rtype: Program
    """
    p = Program()

    # this function allocates the memory values for a parametric program correctly...

    if len(memory_map.keys()) == 0:
        return quil_program
    elif isinstance(list(memory_map.keys())[0], MemoryReference):
        for k, v in memory_map.items():
            p += MOVE(k, v)
    elif isinstance(list(memory_map.keys())[0], str):
        for name, arr in memory_map.items():
            for index, value in enumerate(arr):
                p += MOVE(MemoryReference(name, offset=index), value)
    else:
        raise TypeError(
            "Bad memory_map type; expected Dict[str, List[Union[int, float]]]."
        )

    p += quil_program

    return percolate_declares(p)
Exemple #3
0
    def augment_program_with_memory_values(self, quil_program):
        p = Program()

        for k, v in self._variables_shim.items():
            p += MOVE(MemoryReference(name=k.name, offset=k.index), v)

        p += quil_program

        return percolate_declares(p)
    def augment_program_with_memory_values(quil_program, memory_map):
        p = Program()

        for k, v in memory_map.items():
            p += MOVE(k, v)

        p += quil_program

        return percolate_declares(p)
Exemple #5
0
    def __init__(self, list_gsuit_paulis: List[PauliTerm], qc: QuantumComputer, ref_state: Program, ansatz: Program,
                 shotN: int, parametric_way: bool, n_qubits: int, active_reset: bool = True, cq=None, method='QC',
                 verbose: bool = False):
        """
        A tomography experiment class for use in VQE. In a real experiment, one only has access to measurements in
        the Z-basis, giving a result 0 or 1 for each qubit. The Hamiltonian may have terms like sigma_x or sigma_y,
        for which a basis rotation is required. As quantum mechanics prohibits measurements in multiple bases at once,
        the Hamiltonian needs to be grouped into commuting Pauli terms and for each set of terms the appropriate basis
        rotations is applied to each qubits.

        A parity_matrix is constructed to keep track of what consequence a qubit measurement of 0 or 1 has as a
        contribution to the Pauli estimation.

        The experiments are constructed as objects with class methods to run and adjust them.


        Instantiate using the following parameters:

        :param list() list_gsuit_paulis: list of Pauli terms which can be measured at the same time (they share a TPB!)
        :param QuantumComputer qc: QuantumComputer() object which will simulate the terms
        :param Program ref_state: Program() circuit object which produces the initial reference state (f.ex. Hartree-Fock)
        :param Program ansatz: Program() circuit object which produces the ansatz (f.ex. UCCSD)
        :param int shotN: number of shots to run this Setting for
        :param bool parametric_way: boolean whether to use parametric gates or hard-coded gates
        :param int n_qubits: total number of qubits used for the program
        :param bool active_reset: boolean whether or not to actively reset the qubits
        :param list() cq: list of qubit labels instead of the default [0,1,2,3,...,N-1]
        :param str method: string describing the computational method from {QC, linalg, WFS, Numpy}
        :param bool verbose: boolean, whether or not to give verbose output during execution

        """
        self.parametric_way = parametric_way
        self.pauli_list = list_gsuit_paulis
        self.shotN = shotN
        self.method = method
        self.parity_matrix = self.construct_parity_matrix(list_gsuit_paulis, n_qubits)
        self.verbose = verbose
        self.n_qubits = n_qubits
        self.cq = cq

        if qc is not None:
            if qc.name[-4:] == 'yqvm' and self.cq is not None:
                raise NotImplementedError('manual qubit lattice feed for PyQVM not yet implemented. please set cq=None')
        else:
            if self.method == 'QC':
                raise ValueError('method is QC but no QuantumComputer object supplied.')

        # instantiate a new program and construct it for compilation
        prog = Program()

        if self.method == 'QC':
            ro = prog.declare('ro', memory_type='BIT', memory_size=self.n_qubits)

        if active_reset and self.method == 'QC':
            if not qc.name[-4:] == 'yqvm':  # in case of PyQVM, can not contain reset statement
                prog += RESET()

        # circuit which produces reference state (f.ex. Hartree-Fock)
        prog += ref_state

        # produce which prepares an ansatz state starting from a reference state (f.ex. UCCSD or swap network UCCSD)
        prog += ansatz

        self.coefficients = []
        already_done = []
        for pauli in list_gsuit_paulis:
            # let's store the pauli term coefficients for later use
            self.coefficients.append(pauli.coefficient)

            # also, we perform the necessary rotations going from X or Y to Z basis
            for (i, st) in pauli.operations_as_set():
                if st is not 'I' and i not in already_done:
                    # note that 'i' is the *logical* index corresponding to the pauli.
                    if cq is not None:  # if the logical qubit should be remapped to physical qubits, access this cq
                        prog += pauli_meas(cq[i], st)
                    else:
                        prog += pauli_meas(i, st)
                    # if we already have rotated the basis due to another term, don't do it again!
                    already_done.append(i)

        self.pure_pyquil_program = Program(prog)

        if self.method == 'QC':
            # measure the qubits and assign the result to classical register ro
            for i in range(self.n_qubits):
                if cq is not None:
                    prog += MEASURE(cq[i], ro[i])
                else:
                    prog += MEASURE(i, ro[i])

            prog2 = percolate_declares(prog)

            # wrap in shotN number of executions on the qc, to get operator measurement by sampling
            prog2.wrap_in_numshots_loop(shots=self.shotN)

            self.pyqvm_program = prog2

            # compile to native quil if it's not a PYQVM
            if not qc.name[-4:] == 'yqvm':
                nq_program = qc.compiler.quil_to_native_quil(prog2)
                # if self.verbose:  # debugging purposes
                #    print('')
                #    print(nq_program.native_quil_metadata)
                self.pyquil_executable = qc.compiler.native_quil_to_executable(nq_program)
Exemple #6
0
    def __init__(
        self,
        list_gsuit_paulis: List[PauliTerm],
        qc: QuantumComputer,
        ref_state: Program,
        ansatz: Program,
        shotN: int,
        parametric_way: bool,
        n_qubits: int,
        active_reset: bool = True,
        cq=None,
        method='QC',
        verbose: bool = False,
        calibration: bool = True,
    ):
        """
        A tomography experiment class for use in VQE.
        In a real experiment, one only has access to measurements
        in the Z-basis, giving a result 0 or 1 for each qubit.
        The Hamiltonian may have terms like sigma_x or sigma_y,
        for which a basis rotation is required.
        As quantum mechanics prohibits measurements in multiple bases at once,
        the Hamiltonian needs to be grouped into commuting Pauli terms
        and for each set of terms the appropriate basis
        rotations is applied to each qubits.

        A parity_matrix is constructed to keep track of
        what consequence a qubit measurement of 0 or 1 has as a
        contribution to the Pauli estimation.

        The experiments are constructed as objects with class methods
        to run and adjust them.


        Instantiate using the following parameters:

        :param list() list_gsuit_paulis: list of Pauli terms which
                can be measured at the same time (they share a TPB!)
        :param QuantumComputer qc: QuantumComputer() object which
                will simulate the terms
        :param Program ref_state: Program() circuit object which
                produces the initial reference state (f.ex. Hartree-Fock)
        :param Program ansatz: Program() circuit object which
                produces the ansatz (f.ex. UCCSD)
        :param int shotN: number of shots to run this Setting for
        :param bool parametric_way: boolean whether to
                use parametric gates or hard-coded gates
        :param int n_qubits: total number of qubits used for the program
        :param bool active_reset: boolean whether or not
                to actively reset the qubits
        :param list() cq: list of qubit labels instead of
                the default [0,1,2,3,...,N-1]
        :param str method: string describing the computational
                method from {QC, linalg, WFS, Numpy}
        :param bool verbose: boolean, whether or not to give verbose
                output during execution

        """
        self.parametric_way = parametric_way
        self.pauli_list = list_gsuit_paulis
        self.shotN = shotN
        self.method = method
        self.parity_matrix = self.construct_parity_matrix(
            list_gsuit_paulis, n_qubits)
        self.verbose = verbose
        self.n_qubits = n_qubits
        self.cq = cq
        self.calibration = calibration

        if qc is not None:
            if qc.name[-4:] == 'yqvm' and self.cq is not None:
                raise NotImplementedError(
                    'manual qubit lattice feed'
                    ' for PyQVM not yet implemented. please set cq=None')
        else:
            if self.method == 'QC':
                raise ValueError('method is QC but no QuantumComputer'
                                 ' object supplied.')

        # instantiate a new program and construct it for compilation
        prog = Program()

        if self.method == 'QC':
            ro = prog.declare('ro',
                              memory_type='BIT',
                              memory_size=self.n_qubits)

        if active_reset and self.method == 'QC':
            if not qc.name[-4:] == 'yqvm':
                # in case of PyQVM, can not contain reset statement
                prog += RESET()

        # circuit which produces reference state (f.ex. Hartree-Fock)
        prog += ref_state

        # produce which prepares an ansatz state starting
        # from a reference state (f.ex. UCCSD or swap network UCCSD)
        prog += ansatz

        self.term_keys = []
        already_done = []
        for pauli in list_gsuit_paulis:
            # save the id for each term
            self.term_keys.append(pauli.operations_as_set())

            # also, we perform the necessary rotations
            # going from X or Y to Z basis
            for (i, st) in pauli.operations_as_set():
                if (st == 'X' or st == 'Y') and i not in already_done:
                    # note that 'i' is the *logical* index
                    # corresponding to the pauli.
                    if cq is not None:
                        # if the logical qubit should be remapped
                        # to physical qubits, access this cq
                        prog += pauli_meas(cq[i], st)
                    else:
                        prog += pauli_meas(i, st)
                    # if we already have rotated the basis
                    # due to another term, don't do it again!
                    already_done.append(i)

        if self.method != 'QC':
            self.pure_pyquil_program = Program(prog)
        else:
            # measure the qubits and assign the result
            # to classical register ro
            for i in range(self.n_qubits):
                if cq is not None:
                    prog += MEASURE(cq[i], ro[i])
                else:
                    prog += MEASURE(i, ro[i])

            prog2 = percolate_declares(prog)

            # wrap in shotN number of executions on the qc,
            # to get operator measurement by sampling
            prog2.wrap_in_numshots_loop(shots=self.shotN)

            # print(self.term_keys)
            # print circuits:
            # from pyquil.latex import to_latex
            # print(to_latex(prog2))
            # input()

            if qc.name[-4:] == 'yqvm':
                self.pyqvm_program = prog2
            else:
                self.pyquil_executable = qc.compile(prog2)

            # print("compiled")
            # print(to_latex(qc.compiler.quil_to_native_quil(prog2)))
            # input()

        # now about calibration
        if self.calibration and self.method != 'QC':
            # turn off calibration if not QC.
            self.calibration = False
        if self.calibration:
            # prepare and run the calibration experiments
            prog = Program()
            ro = prog.declare('ro',
                              memory_type='BIT',
                              memory_size=self.n_qubits)

            if active_reset:
                if not qc.name[-4:] == 'yqvm':
                    # in case of PyQVM, can not contain reset statement
                    prog += RESET()

            # circuit which produces reference state,
            # which is in the case of calibration experiments
            # the same as the out_operators.
            already_done = []
            for pauli in list_gsuit_paulis:
                # also, we perform the necessary rotations
                # going to X/Y/Z =1 state
                for (i, st) in pauli.operations_as_set():
                    if (st == 'X' or st == 'Y') and i not in already_done:
                        # note that 'i' is the *logical* index
                        # corresponding to the pauli.
                        if cq is not None:
                            # if the logical qubit should be remapped
                            # to physical qubits, access this cq
                            prog += pauli_meas(cq[i], st).dagger()
                        else:
                            prog += pauli_meas(i, st).dagger()
                        # if we already have rotated the basis
                        # due to another term, don't do it again!
                        already_done.append(i)
            # measurement now
            already_done = []
            for pauli in list_gsuit_paulis:
                # also, we perform the necessary rotations
                # going from X or Y to Z basis
                for (i, st) in pauli.operations_as_set():
                    if (st == 'X' or st == 'Y') and i not in already_done:
                        # note that 'i' is the *logical* index
                        # corresponding to the pauli.
                        if cq is not None:
                            # if the logical qubit should be remapped
                            # to physical qubits, access this cq
                            prog += pauli_meas(cq[i], st)
                        else:
                            prog += pauli_meas(i, st)
                        # if we already have rotated the basis
                        # due to another term, don't do it again!
                        already_done.append(i)

            # measure the qubits and assign the result
            # to classical register ro
            for i in range(self.n_qubits):
                if cq is not None:
                    prog += MEASURE(cq[i], ro[i])
                else:
                    prog += MEASURE(i, ro[i])

            prog2 = percolate_declares(prog)
            # wrap in shotN number of executions on the qc,
            # to get operator measurement by sampling
            prog2.wrap_in_numshots_loop(shots=self.shotN)

            if qc.name[-4:] == 'yqvm':
                self.pyqvm_program = prog2
                bitstrings = qc.run(self.pyqvm_program)
            # compile to native quil if it's not a PYQVM
            else:
                pyquil_executable = qc.compile(prog2)
                bitstrings = qc.run(pyquil_executable)
            # start data processing
            # this matrix computes the pauli string parity,
            # and stores that for each bitstring
            is_odd = np.mod(bitstrings.dot(self.parity_matrix), 2)
            # if the parity is odd, the bitstring gives a -1 eigenvalue,
            # and +1 vice versa.
            # sum over all bitstrings, average over shotN shots,
            # and weigh each pauli string by its coefficient
            e_array = 1 - 2 * np.sum(is_odd, axis=0) / self.shotN
            self.calibration_norms = e_array
            print(f"calibration_norms: {e_array}")