Beispiel #1
0
def test_get_qubits():
    pq = Program(X(0), CNOT(0, 4), MEASURE(5, [5]))
    assert pq.get_qubits() == {0, 4, 5}

    qq = pq.alloc()
    pq.inst(Y(2), X(qq))
    assert pq.get_qubits() == {0, 1, 2, 4, 5}  # this synthesizes the allocation
Beispiel #2
0
def test_get_qubits():
    pq = Program(X(0), CNOT(0, 4), MEASURE(5, [5]))
    assert pq.get_qubits() == {0, 4, 5}

    qq = pq.alloc()
    pq.inst(Y(2), X(qq))
    assert pq.get_qubits() == {0, 1, 2, 4, 5}  # this synthesizes the allocation

    qubit_index = 1
    p = Program(("H", qubit_index))
    assert p.get_qubits() == {qubit_index}
    q1 = p.alloc()
    q2 = p.alloc()
    p.inst(("CNOT", q1, q2))
    assert p.get_qubits() == {qubit_index, 0, 2}
Beispiel #3
0
    def run_and_measure(self,
                        quil_program: Program,
                        qubits: List[int] = None,
                        trials: int = 1) -> np.ndarray:
        """
        Run a Quil program once to determine the final wavefunction, and measure multiple times.

        Alternatively, consider using ``wavefunction`` and calling ``sample_bitstrings`` on the
        resulting object.

        For a large wavefunction and a low-medium number of trials, use this function.
        On the other hand, if you're sampling a small system many times you might want to
        use ``Wavefunction.sample_bitstrings``.

        .. note:: If your program contains measurements or noisy gates, this method may not do what
            you want. If the execution of ``quil_program`` is **non-deterministic** then the
            final wavefunction from which the returned bitstrings are sampled itself only
            represents a stochastically generated sample and the outcomes sampled from
            *different* ``run_and_measure`` calls *generally sample different bitstring
            distributions*.

        :param quil_program: The program to run and measure
        :param qubits: An optional list of qubits to measure. The order of this list is
            respected in the returned bitstrings. If not provided, all qubits used in
            the program will be measured and returned in their sorted order.
        :param int trials: Number of times to sample from the prepared wavefunction.
        :return: An array of measurement results (0 or 1) of shape (trials, len(qubits))
        """
        if qubits is None:
            qubits = sorted(quil_program.get_qubits(indices=True))

        return self.connection._run_and_measure(quil_program=quil_program,
                                                qubits=qubits,
                                                trials=trials,
                                                random_seed=self.random_seed)
Beispiel #4
0
    def run_and_benchmark_program(self, prog: Program, trials: int,
                                  is_correct: Callable[[List[int]], bool],
                                  separate: bool) -> int:
        n_qubits = len(prog.get_qubits())
        qvm = pyquil.get_qc("{}q-qvm".format(n_qubits))

        # The paramaters are 10x less noisy than the defaults.
        qvm.qam.noise_model = self.noise_model(qvm.device)

        if separate:
            elapsed = []
            results = []
            for i in range(trials):
                start_time = time.time()
                trial_results = qvm.run(prog)
                end_time = time.time()

                elapsed.append(end_time - start_time)
                results.extend(trial_results)
        else:
            prog.wrap_in_numshots_loop(trials)
            start_time = time.time()
            results = qvm.run(prog)
            end_time = time.time()
            elapsed = end_time - start_time

        correct = sum(is_correct(result) for result in results)
        return correct, elapsed
Beispiel #5
0
    def _run(self,
             circuit: Program,
             num_shots: int = 8192) -> List[np.ndarray]:
        """Run a Program a number of times and record the measurement results.
        
        Args:
            circuit: A Program to be run and measured.
            num_shots: An integer representing the number of times the quantum circuit is to be evaluated.
            
        Returns: A list of numpy arrays containing the readout of qubits from single measurements.
        
        """
        prog = circuit.copy()

        #check whether we are given any bit-flip probabilities
        if self._noisy_readout_probabilities:

            # Add readout error to each qubit according to the dictionary
            for qubit in circuit.get_qubits(True):

                # it might happen that there are qubits without any probabilities given which would raise a KeyError
                try:
                    p0, p1 = self._noisy_readout_probabilities[qubit]
                    prog.define_noisy_readout(qubit, p00=p0, p11=p1)
                except:

                    # if we don't find the key in the dictionary, we don't add readout error the the qubit, but rather pass
                    pass

        # execute the circuit num_shots times
        prog.wrap_in_numshots_loop(num_shots)
        result = self._device.run(self._device.compile(prog))

        return result
Beispiel #6
0
def generate_single_depth_experiment(rotation: Program,
                                     depth: int,
                                     exp_type: str,
                                     axis: Tuple = None) -> Program:
    """
    Generate an experiment for a single depth where the type specifies a final measurement of either
    X or Y. The rotation program is repeated depth number of times, and we assume the rotation is
    about the axis (theta, phi).

    :param rotation: the program specifying the gate whose angle of rotation we wish to estimate.
    :param depth: the number of times we apply the rotation in the experiment
    :param exp_type: X or Y, specifying which operator to measure at the end of the experiment
    :param axis: the axis of rotation. If none is specified, axis is assumed to be the Z axis. (rotation should be RZ)
    :return: a program specifying the entire experiment of a single iteration of the RPE protocol in [RPE]
    """
    experiment = Program()
    ro_bit = experiment.declare("ro", "BIT", 1)
    qubit = list(rotation.get_qubits())[0]
    prepare_state(experiment, qubit, axis)
    for _ in range(depth):
        experiment.inst(rotation)
    if axis:
        experiment.inst(RZ(-axis[1], qubit))
        experiment.inst(RY(-axis[0], qubit))
    local_pauli_eig_meas(experiment, exp_type, qubit)
    experiment.measure(qubit, ro_bit)
    return experiment
Beispiel #7
0
def test_get_qubits():
    pq = Program(X(0), CNOT(0, 4), MEASURE(5, 5))
    assert pq.get_qubits() == {0, 4, 5}

    q = [QubitPlaceholder() for _ in range(6)]
    pq = Program(X(q[0]), CNOT(q[0], q[4]), MEASURE(q[5], 5))
    qq = pq.alloc()
    pq.inst(Y(q[2]), X(qq))
    assert address_qubits(pq).get_qubits() == {0, 1, 2, 3, 4}

    qubit_index = 1
    p = Program(("H", qubit_index))
    assert p.get_qubits() == {qubit_index}
    q1 = p.alloc()
    q2 = p.alloc()
    p.inst(("CNOT", q1, q2))
    with pytest.raises(ValueError) as e:
        _ = address_qubits(p).get_qubits()
    assert e.match('Your program mixes instantiated qubits with placeholders')
def append_measure_register(program: Program,
                            qubits: List = None,
                            trials: int = 10,
                            ham: PauliSum = None) -> Program:
    """Creates readout register, MEASURE instructions for register and wraps
    in trials trials.

    Parameters
    ----------
    param qubits : list
        List of Qubits to measure. If None, program.get_qubits() is used
    param trials : int
        The number of trials to run.
    param ham : PauliSum
        Hamiltonian to whose basis we need to switch. All terms in it must
        trivially commute!

    Returns
    -------
    Program :
        program with the gate change and measure instructions appended
    """
    base_change_gates = {'X': lambda qubit: H(qubit),
                         'Y': lambda qubit: RX(np.pi / 2, qubit),
                         'Z': lambda qubit: I(qubit)}

    if qubits is None:
        qubits = program.get_qubits()


    def _get_correct_gate(qubit: Union[int, QubitPlaceholder]) -> Program():
        """Correct base change gate on the qubit `qubit` given `ham`"""
        # this is an extra function, because `return` allows us to
        # easily break out of loops
        for term in ham:
            if term[qubit] != 'I':
                return base_change_gates[term[qubit]](qubit)

        raise ValueError(f"PauliSum {ham} doesn't act on qubit {qubit}")

    # append to correct base change gates if ham is specified. Otherwise
    # assume diagonal basis
    if ham is not None:
        for qubit in ham.get_qubits():
            program += Program(_get_correct_gate(qubit))

    # create a read out register
    ro = program.declare('ro', memory_type='BIT', memory_size=len(qubits))

    # add measure instructions to the specified qubits
    for i, qubit in enumerate(qubits):
        program += MEASURE(qubit, ro[i])
    program.wrap_in_numshots_loop(trials)
    return program
Beispiel #9
0
    def run_and_measure(
        self,
        quil_program: Program,
        qubits: Optional[List[int]] = None,
        trials: int = 1,
        memory_map: Optional[Union[Dict[str, List[Union[int, float]]],
                                   Dict[MemoryReference, Any]]] = None,
    ) -> np.ndarray:
        """
        Run a Quil program once to determine the final wavefunction, and measure multiple times.

        Alternatively, consider using ``wavefunction`` and calling ``sample_bitstrings`` on the
        resulting object.

        For a large wavefunction and a low-medium number of trials, use this function.
        On the other hand, if you're sampling a small system many times you might want to
        use ``Wavefunction.sample_bitstrings``.

        .. note:: If your program contains measurements or noisy gates, this method may not do what
            you want. If the execution of ``quil_program`` is **non-deterministic** then the
            final wavefunction from which the returned bitstrings are sampled itself only
            represents a stochastically generated sample and the outcomes sampled from
            *different* ``run_and_measure`` calls *generally sample different bitstring
            distributions*.

        :param quil_program: The program to run and measure
        :param qubits: An optional list of qubits to measure. The order of this list is
            respected in the returned bitstrings. If not provided, all qubits used in
            the program will be measured and returned in their sorted order.
        :param int trials: Number of times to sample from the prepared wavefunction.
        :param memory_map: An assignment of classical registers to values, representing an initial
                           state for the QAM's classical memory.

                           This is expected to be of type Dict[str, List[Union[int, float]]],
                           where the keys are memory region names and the values are arrays of
                           initialization data.

                           For now, we also support input of type Dict[MemoryReference, Any],
                           but this is deprecated and will be removed in a future release.
        :return: An array of measurement results (0 or 1) of shape (trials, len(qubits))
        """
        if qubits is None:
            qubits = sorted(
                cast(Set[int], quil_program.get_qubits(indices=True)))

        if memory_map is not None:
            quil_program = self.augment_program_with_memory_values(
                quil_program, memory_map)

        return self.connection._run_and_measure(quil_program=quil_program,
                                                qubits=qubits,
                                                trials=trials,
                                                random_seed=self.random_seed)
Beispiel #10
0
def test_get_qubits():
    pq = Program(Declare("ro", "BIT"), X(0), CNOT(0, 4),
                 MEASURE(5, MemoryReference("ro", 0)))
    assert pq.get_qubits() == {0, 4, 5}

    q = [QubitPlaceholder() for _ in range(6)]
    pq = Program(Declare("ro", "BIT"), X(q[0]), CNOT(q[0], q[4]),
                 MEASURE(q[5], MemoryReference("ro", 0)))
    qq = QubitPlaceholder()
    pq.inst(Y(q[2]), X(qq))
    assert address_qubits(pq).get_qubits() == {0, 1, 2, 3, 4}

    qubit_index = 1
    p = Program(("H", qubit_index))
    assert p.get_qubits() == {qubit_index}
    q1 = QubitPlaceholder()
    q2 = QubitPlaceholder()
    p.inst(("CNOT", q1, q2))
    with pytest.raises(ValueError) as e:
        _ = address_qubits(p).get_qubits()
    assert e.match("Your program mixes instantiated qubits with placeholders")
    def run_and_measure(self, program: Program,
                        trials: int) -> Dict[int, np.ndarray]:
        """
        Run the provided state preparation program and measure all qubits.

        The returned data is a dictionary keyed by qubit index because qubits for a given
        QuantumComputer may be non-contiguous and non-zero-indexed. To turn this dictionary
        into a 2d numpy array of bitstrings, consider::

            bitstrings = qc.run_and_measure(...)
            bitstring_array = np.vstack([bitstrings[q] for q in qc.qubits()]).T
            bitstring_array.shape  # (trials, len(qc.qubits()))

        .. note::

            If the target :py:class:`QuantumComputer` is a noiseless :py:class:`QVM` then
            only the qubits explicitly used in the program will be measured. Otherwise all
            qubits will be measured. In some circumstances this can exhaust the memory
            available to the simulator, and this may be manifested by the QVM failing to
            respond or timeout.

        .. note::

            In contrast to :py:class:`QVMConnection.run_and_measure`, this method simulates
            noise correctly for noisy QVMs. However, this method is slower for ``trials > 1``.
            For faster noise-free simulation, consider
            :py:class:`WavefunctionSimulator.run_and_measure`.

        :param program: The state preparation program to run and then measure.
        :param trials: The number of times to run the program.
        :return: A dictionary keyed by qubit index where the corresponding value is a 1D array of
            measured bits.
        """
        program = program.copy()
        validate_supported_quil(program)
        ro = program.declare("ro", "BIT", len(self.qubits()))
        measure_used = isinstance(self.qam,
                                  QVM) and self.qam.noise_model is None
        qubits_to_measure = set(
            map(qubit_index, program.get_qubits()) if measure_used else self.
            qubits())
        for i, q in enumerate(qubits_to_measure):
            program.inst(MEASURE(q, ro[i]))
        program.wrap_in_numshots_loop(trials)
        executable = self.compile(program)
        bitstring_array = self.run(executable=executable)
        bitstring_dict = {}
        for i, q in enumerate(qubits_to_measure):
            bitstring_dict[q] = bitstring_array[:, i]
        for q in set(self.qubits()) - set(qubits_to_measure):
            bitstring_dict[q] = np.zeros(trials)
        return bitstring_dict
Beispiel #12
0
def generate_2q_single_depth_experiment(rotation: Program,
                                        depth: int,
                                        exp_type: str,
                                        measurement_qubit: int,
                                        init_one: bool = False,
                                        axis: Tuple = None) -> Program:
    """
    A special variant of the 1q method that is specifically designed to calibrate a CPHASE gate. The
    ideal CPHASE is of the following form
        CPHASE(\phi) = diag(1,1,1,Exp[-i \phi]
    The imperfect CPHASE has two local Z rotations and a possible over (or under) rotation on the
    phase phi. Thus we have
        CPHASE(\Phi, \Theta_1, \Theta_2) = diag( exp(-a -b), exp(-a + b), exp(a-b), exp(a+b+c) )
        a = i \Theta_1 / 2,     b = i \Theta_2 / 2,     c = i \Phi

    The following experiments isolate the three angles using the state preparations |0>|+>, |+>|0>,
    |1>|+>, |+>|1> where the incurred phase is measured on the qubit initialized to the plus state.
    The four measurements are specified by setting the measurement qubit to either q1 or q2, and
    setting init_one to True indicating that the non-measurement qubit be prepared in the one state
    |1>.

    :param rotation: the program specifying the gate whose angle of rotation we wish to estimate.
    :param depth: the number of times we apply the rotation in the experiment
    :param exp_type: X or Y, specifying which operator to measure at the end of the experiment
    :param measurement_qubit: the qubit to be measured in this variant of the experiment
    :param axis: the axis of rotation. If none is specified, axis is assumed to be the Z axis. (rotation should be RZ)
    :param init_one: True iff the non-measurement qubit should be prepared in the 1 state.
    :param axis: the axis of rotation. If none is specified, axis is assumed to be the Z axis. (rotation should be RZ)
    :return: An estimate of some aspect of the CPHASE gate which depends on the measurement variant.
    """
    prog = Program()
    ro_bit = prog.declare("ro", "BIT", 1)
    qubits = rotation.get_qubits()
    non_measurement_qubit = list(qubits - {measurement_qubit})[0]
    prepare_state(prog, measurement_qubit)
    if init_one:
        prog.inst(X(non_measurement_qubit))
    for _ in range(depth):
        prog.inst(rotation)
    if axis:
        prog.inst(RZ(-axis[1], measurement_qubit))
        prog.inst(RY(-axis[0], measurement_qubit))
    local_pauli_eig_meas(prog, exp_type, measurement_qubit)
    prog.measure(measurement_qubit, ro_bit)
    return prog
Beispiel #13
0
def generate_state_dfe_experiment(prog: Program, compiler) -> DFEexperiment:
    """
    Generate a namedtuple containing all the experiments needed to perform direct fidelity estimation
    of a state.

    The experiments are represented by: input Pauli operators, whose eigenstates are the
    preperations; the programs specified by the user; and the output Pauli operators which
    specify the measurements that need to be performed.

    :param prog: A PyQuil program for preparing the state to be characterized. Must consist
    only of elements of the Clifford group.
    :param compiler: PyQuil compiler connection.
    :return: A 'DFEexperiment'
    """
    qubits = prog.get_qubits()
    n_qubits = len(qubits)
    inpaulis = all_pauli_z_terms(n_qubits, qubits)
    outpaulis = [compiler.apply_clifford_to_pauli(prog, pauli) for pauli in inpaulis]
    return DFEexperiment(in_pauli=inpaulis, program=prog, out_pauli=outpaulis)
Beispiel #14
0
    def apply_clifford_to_pauli(self, clifford: Program,
                                pauli_in: PauliTerm) -> PauliTerm:
        r"""
        Given a circuit that consists only of elements of the Clifford group,
        return its action on a PauliTerm.

        In particular, for Clifford C, and Pauli P, this returns the PauliTerm
        representing CPC^{\dagger}.

        :param clifford: A Program that consists only of Clifford operations.
        :param pauli_in: A PauliTerm to be acted on by clifford via conjugation.
        :return: A PauliTerm corresponding to clifford * pauli_in * clifford^{\dagger}
        """
        # do nothing if `pauli_in` is the identity
        if is_identity(pauli_in):
            return pauli_in

        indices_and_terms = list(zip(*list(pauli_in.operations_as_set())))

        payload = ConjugateByCliffordRequest(
            clifford=clifford.out(),
            pauli=rpcq.messages.PauliTerm(indices=list(indices_and_terms[0]),
                                          symbols=list(indices_and_terms[1])),
        )
        response: ConjugateByCliffordResponse = self.client.call(
            "conjugate_pauli_by_clifford", payload)
        phase_factor, paulis = response.phase, response.pauli

        pauli_out = PauliTerm("I", 0, 1.0j**phase_factor)
        clifford_qubits = clifford.get_qubits()
        pauli_qubits = pauli_in.get_qubits()
        all_qubits = sorted(
            set(cast(List[int],
                     pauli_qubits)).union(set(cast(List[int],
                                                   clifford_qubits))))
        # The returned pauli will have specified its value on all_qubits, sorted by index.
        #  This is maximal set of qubits that can be affected by this conjugation.
        for i, pauli in enumerate(paulis):
            pauli_out = cast(PauliTerm,
                             pauli_out * PauliTerm(pauli, all_qubits[i]))
        return cast(PauliTerm, pauli_out * pauli_in.coefficient)
Beispiel #15
0
def generate_process_dfe_experiment(prog: Program, compiler) -> DFEexperiment:
    """
    Generate a namedtuple containing all the experiments needed to perform direct fidelity estimation
    of a process.

    The experiments are represented by: input Pauli operators, whose eigenstates are the
    preperations; the programs specified by the user; and the output Pauli operators which
    specify the measurements that need to be performed.

    :param prog: A PyQuil program for preparing the unitary to be characterized. Must consist
    only of elements of the Clifford group.
    :param compiler: PyQuil compiler connection.
    :return: A namedtuple, called 'dfe_experiment', containing
        in_pauli - The Pauli being acted on by prog.
        program - The program the user wants to perform DFE on.
        out_pauli - The Pauli that should be produced after prog acts on in_prog.
    """
    qubits = prog.get_qubits()
    n_qubits = len(qubits)
    inpaulis = all_pauli_terms(n_qubits, qubits)
    outpaulis = [compiler.apply_clifford_to_pauli(prog, pauli) for pauli in inpaulis]
    return DFEexperiment(in_pauli=inpaulis, program=prog, out_pauli=outpaulis)
Beispiel #16
0
def test_get_qubits_not_as_indices():
    pq = Program(Declare("ro", "BIT"), X(0), CNOT(0, 4),
                 MEASURE(5, MemoryReference("ro", 0)))
    assert pq.get_qubits(indices=False) == {Qubit(i) for i in [0, 4, 5]}
Beispiel #17
0
def test_get_qubit_placeholders():
    qs = QubitPlaceholder.register(8)
    pq = Program(Declare("ro", "BIT"), X(qs[0]), CNOT(qs[0], qs[4]),
                 MEASURE(qs[5], MemoryReference("ro", 0)))
    assert pq.get_qubits() == {qs[i] for i in [0, 4, 5]}
    def run_symmetrized_readout(
        self,
        program: Program,
        trials: int,
        symm_type: int = 3,
        meas_qubits: Optional[List[int]] = None,
    ) -> np.ndarray:
        r"""
        Run a quil program in such a way that the readout error is made symmetric. Enforcing
        symmetric readout error is useful in simplifying the assumptions in some near
        term error mitigation strategies, see ``measure_observables`` for more information.

        The simplest example is for one qubit. In a noisy device, the probability of accurately
        reading the 0 state might be higher than that of the 1 state; due to e.g. amplitude
        damping. This makes correcting for readout more difficult. In the simplest case, this
        function runs the program normally ``(trials//2)`` times. The other half of the time,
        it will insert an ``X`` gate prior to any ``MEASURE`` instruction and then flip the
        measured classical bit back. Overall this has the effect of symmetrizing the readout error.

        The details. Consider preparing the input bitstring ``|i>`` (in the computational basis) and
        measuring in the Z basis. Then the Confusion matrix for the readout error is specified by
        the probabilities

             p(j|i) := Pr(measured = j | prepared = i ).

        In the case of a single qubit i,j \in [0,1] then:
        there is no readout error if p(0|0) = p(1|1) = 1.
        the readout error is symmetric if p(0|0) = p(1|1) = 1 - epsilon.
        the readout error is asymmetric if p(0|0) != p(1|1).

        If your quantum computer has this kind of asymmetric readout error then
        ``qc.run_symmetrized_readout`` will symmetrize the readout error.

        The readout error above is only asymmetric on a single bit. In practice the confusion
        matrix on n bits need not be symmetric, e.g. for two qubits p(ij|ij) != 1 - epsilon for
        all i,j. In these situations a more sophisticated means of symmetrization is needed; and
        we use orthogonal arrays (OA) built from Hadamard matrices.

        The symmetrization types are specified by an int; the types available are:
        -1 -- exhaustive symmetrization uses every possible combination of flips
        0 -- trivial that is no symmetrization
        1 -- symmetrization using an OA with strength 1
        2 -- symmetrization using an OA with strength 2
        3 -- symmetrization using an OA with strength 3
        In the context of readout symmetrization the strength of the orthogonal array enforces
        the symmetry of the marginal confusion matrices.

        By default a strength 3 OA is used; this ensures expectations of the form
        ``<b_k . b_j . b_i>`` for bits any bits i,j,k will have symmetric readout errors. Here
        expectation of a random variable x as is denote ``<x> = sum_i Pr(i) x_i``. It turns out that
        a strength 3 OA is also a strength 2 and strength 1 OA it also ensures ``<b_j . b_i>`` and
        ``<b_i>`` have symmetric readout errors for any bits b_j and b_i.

        :param program: The program to run symmetrized readout on.
        :param trials: The minimum number of times to run the program; it is recommend that this
            number should be in the hundreds or thousands. This parameter will be mutated if
            necessary.
        :param symm_type: the type of symmetrization
        :param meas_qubits: An advanced feature. The groups of measurement qubits. Only these
            qubits will be symmetrized over, even if the program acts on other qubits.
        :return: A numpy array of shape (trials, len(ro-register)) that contains 0s and 1s.
        """
        if not isinstance(symm_type, int):
            raise ValueError(
                "Symmetrization options are indicated by an int. See "
                "the docstrings for more information.")

        if meas_qubits is None:
            meas_qubits = list(cast(Set[int], program.get_qubits()))

        # It is desirable to have hundreds or thousands of trials more than the minimum
        trials = _check_min_num_trials_for_symmetrized_readout(
            len(meas_qubits), trials, symm_type)

        sym_programs, flip_arrays = _symmetrization(program, meas_qubits,
                                                    symm_type)

        # Floor division so e.g. 9 // 8 = 1 and 17 // 8 = 2.
        num_shots_per_prog = trials // len(sym_programs)

        if num_shots_per_prog * len(sym_programs) < trials:
            warnings.warn(
                f"The number of trials was modified from {trials} to "
                f"{num_shots_per_prog * len(sym_programs)}. To be consistent with the "
                f"number of trials required by the type of readout symmetrization "
                f"chosen.")

        results = _measure_bitstrings(self, sym_programs, meas_qubits,
                                      num_shots_per_prog)

        return _consolidate_symmetrization_outputs(results, flip_arrays)
Beispiel #19
0
def test_get_qubits_not_as_indices():
    pq = Program(X(0), CNOT(0, 4), MEASURE(5, 5))
    assert pq.get_qubits(indices=False) == {Qubit(i) for i in [0, 4, 5]}
Beispiel #20
0
def test_get_qubit_placeholders():
    qs = QubitPlaceholder.register(8)
    pq = Program(X(qs[0]), CNOT(qs[0], qs[4]), MEASURE(qs[5], 5))
    assert pq.get_qubits() == {qs[i] for i in [0, 4, 5]}
Beispiel #21
0
def rewrite_program(raw_prog: Program, qecc: QECC) -> Program:
    if qecc.k != 1:
        raise UnsupportedQECCError("code must have k = 1")

    if raw_prog.defined_gates:
        raise UnsupportedProgramError("does not support DEFGATE")

    # Assign indices to qubit placeholders in the raw program.
    raw_prog = quil.address_qubits(raw_prog)

    new_prog = Program()

    logical_qubits = {
        index: new_logical_qubit(new_prog, qecc,
                                 "logical_qubit_{}".format(index))
        for index in raw_prog.get_qubits(indices=True)
    }

    # Construct ancilla code blocks.
    ancilla_1 = new_logical_qubit(new_prog, qecc, "ancilla_1")
    ancilla_2 = new_logical_qubit(new_prog, qecc, "ancilla_2")

    # Classical scratch BIT registers for gates/measurements.
    scratch_size = max(qecc.n, qecc.measure_scratch_size)
    raw_scratch = new_prog.declare('scratch', 'BIT', scratch_size)
    scratch = MemoryChunk(raw_scratch, 0, raw_scratch.declared_size)
    _initialize_memory(new_prog, raw_scratch,
                       ancilla_1.qubits + ancilla_2.qubits)

    # Classical scratch INTEGER registers.
    raw_scratch_int = new_prog.declare('scratch_int', 'INTEGER', 2)
    scratch_int = MemoryChunk(raw_scratch_int, 0,
                              raw_scratch_int.declared_size)
    _initialize_memory(new_prog, raw_scratch_int,
                       ancilla_1.qubits + ancilla_2.qubits)

    perform_error_correction = _make_error_corrector(new_prog, qecc, ancilla_1,
                                                     ancilla_2)

    # Reset all logical qubits.
    for block in logical_qubits.values():
        qecc.encode_zero(new_prog, block, ancilla_1, scratch)

    for inst in raw_prog.instructions:
        if isinstance(inst, Gate):
            gate_qubits = [
                logical_qubits[index] for index in _gate_qubits(inst)
            ]
            qecc.apply_gate(new_prog, inst.name, *gate_qubits)

            # Perform error correction after every logical gate.
            perform_error_correction(logical_qubits.values())
        elif isinstance(inst, Measurement):
            qubit = logical_qubits[_extract_qubit_index(inst.qubit)]
            # This should really use its own ancilla instead of sharing with the error correction,
            # but we need be extremely conservative with the number of qubits.
            for _ in qecc.measure(new_prog, qubit, 0, inst.classical_reg,
                                  ancilla_1, ancilla_2, scratch, scratch_int):
                # Since measurements are taken multiple times for redundancy, we need to perform
                # rounds of error correction during the measurement routine.
                perform_error_correction(logical_qubits.values())
        elif isinstance(inst, ResetQubit):
            raise NotImplementedError(
                "this instruction is not in the Quil spec")
        elif isinstance(inst, JumpTarget):
            new_prog.inst(JumpTarget(_mangle_label(inst.label)))
        elif isinstance(inst, JumpConditional):
            new_prog.inst(
                type(inst)(_mangle_label(inst.target), inst.condition))
        elif isinstance(inst, Jump):
            new_prog.inst(Jump(_mangle_label(inst.target)))
        elif isinstance(inst, Halt):
            new_prog.append(inst)
        elif isinstance(inst, Wait):
            raise NotImplementedError()
        elif isinstance(inst, Reset):
            for block in logical_qubits.values():
                qecc.encode_zero(new_prog, block.qubits, ancilla_1, scratch)
        elif isinstance(inst, Declare):
            new_prog.inst(inst)
        elif isinstance(inst, Pragma):
            new_prog.inst(inst)
        elif any(
                isinstance(inst, ClassicalInst)
                for ClassicalInst in CLASSICAL_INSTRUCTIONS):
            new_prog.inst(inst)
        else:
            raise UnsupportedProgramError("unsupported instruction: {}", inst)

    return quil.address_qubits(new_prog)
Beispiel #22
0
    def native_quil_to_executable(self, nq_program: Program) -> Optional[Circuit]:
        """
        Compiles a Quil program to a :class:`qscout.core.Circuit`. Because Quil
        does not support any form of schedule control, the entire circuit will be put in a
        single unscheduled block. If the :mod:`qscout.scheduler` is run on the circuit, as
        many as possible of those gates will be parallelized, while maintaining the order
        of gates that act on the same qubits. Otherwise, the circuit will be treated as a
        fully sequential circuit.

        Measurement and reset commands are supported, but only if applied to every qubit in
        the circuit in immediate succession. If so, they will be mapped to a prepare_all or
        measure_all gate. If the circuit does not end with a measurement, then a measure_all
        gate will be appended to it.

        :param pyquil.quil.Program nq_program: The program to compile.
        :returns: The same quantum program, converted to JaqalPaq.
        :rtype: qscout.core.Circuit
        :raises JaqalError: If the program includes a non-gate instruction other than resets or measurements.
        :raises JaqalError: If the user tries to measure or reset only some of the qubits, rather than all of them.
        :raises JaqalError: If the program includes a gate not included in `names`.
        """
        n = max(nq_program.get_qubits()) + 1
        if n > len(self._device.qubits()):
            raise JaqalError(
                "Program uses more qubits (%d) than device supports (%d)."
                % (n, len(self._device.qubits()))
            )
        qsc = CircuitBuilder(native_gates=self.native_gates)
        block = UnscheduledBlockBuilder()
        qsc.expression.append(block.expression)
        # Quil doesn't support barriers, so either the user
        # won't run the the scheduler and everything will happen
        # sequentially, or the user will and everything can be
        # rescheduled as needed.
        qreg = qsc.register("qreg", n)
        block.gate("prepare_all")
        reset_accumulator = set()
        measure_accumulator = set()
        in_preamble = True
        for instr in nq_program:
            if reset_accumulator:
                if isinstance(instr, ResetQubit):
                    reset_accumulator.add(instr.qubit.index)
                    if nq_program.get_qubits() <= reset_accumulator:
                        block.gate("prepare_all")
                        reset_accumulator = set()
                        in_preamble = False
                    continue
                else:
                    raise JaqalError(
                        "Cannot reset only qubits %s and not whole register."
                        % reset_accumulator
                    )
                    # reset_accumulator = set()
            if measure_accumulator:
                if isinstance(instr, Measurement):
                    measure_accumulator.add(instr.qubit.index)
                    if nq_program.get_qubits() <= measure_accumulator:
                        block.gate("measure_all")
                        measure_accumulator = set()
                        in_preamble = False
                    continue
                else:
                    raise JaqalError(
                        "Cannot measure only qubits %s and not whole register."
                        % measure_accumulator
                    )
                    # measure_accumulator = set()
            if isinstance(instr, Gate):
                if instr.name in self.names:
                    block.gate(
                        self.names[instr.name],
                        *[qreg[qubit.index] for qubit in instr.qubits],
                        *[float(p) for p in instr.params]
                    )
                    in_preamble = False
                else:
                    raise JaqalError("Gate %s not in native gate set." % instr.name)
            elif isinstance(instr, Reset):
                if not in_preamble:
                    block.gate("prepare_all")
                    in_preamble = False
            elif isinstance(instr, ResetQubit):
                if not in_preamble:
                    reset_accumulator = {instr.qubit.index}
                    if nq_program.get_qubits() <= reset_accumulator:
                        block.gate("prepare_all")
                        reset_accumulator = set()
            elif isinstance(instr, Measurement):
                measure_accumulator = {instr.qubit.index}
                # We ignore the classical register.
                if nq_program.get_qubits() <= measure_accumulator:
                    block.gate("measure_all")
                    measure_accumulator = set()
                    in_preamble = False
            elif isinstance(instr, Declare):
                pass  # Ignore allocations of classical memory.
            else:
                raise JaqalError("Instruction %s not supported." % instr.out())
        block.gate("measure_all", no_duplicate=True)
        return qsc.build()
Beispiel #23
0
def generate_rpe_experiment(
        rotation: Program,
        change_of_basis: Union[np.ndarray, Program],
        measure_qubits: Sequence[int] = None,
        num_depths: int = 6,
        prepare_and_post_select: Dict[int, int] = None) -> DataFrame:
    """
    Generate a dataframe containing all the experiments needed to perform robust phase estimation
    to estimate the angle of rotation of the given rotation program.

    In general, this experiment consists of multiple iterations of the following steps performed for
    different depths and measurement in different "directions":
        1) Prepare the equal superposition between computational basis states (i.e. the
            eigenvectors of a rotation about the Z axis)
        2) Perform a change of basis which maps the computational basis to eigenvectors of the
            rotation.
        3) Perform the rotation depth-many times, where depth=2^iteration number. Each
            eigenvector component picks up a phase from the rotation. In the 1-qubit case this
            means that the state rotates about the axis formed by the eigenvectors at a rate
            which is given by the relative phase between the two eigenvector components.
        4) Invert the change of basis to return to the computational basis.
        5) Prepare (one of) the qubit(s) for measurement along either the X or Y axis.
        6) Measure this qubit, and in the multi-qubit case other qubits participating in rotation.
    Measure_qubits can be used e.g. in the case of noisy cross-talk to measure the effective
    action of some "rotation program" that acts on completely different qubits but nonetheless
    rotates each measure_qubit.

    The single qubit algorithm is due to:

    [RPE]  Robust Calibration of a Universal Single-Qubit Gate-Set via Robust Phase Estimation
           Kimmel et al.,
           Phys. Rev. A 92, 062315 (2015)
           https://doi.org/10.1103/PhysRevA.92.062315
           https://arxiv.org/abs/1502.02677

    [RPE2] Experimental Demonstration of a Cheap and Accurate Phase Estimation
           Rudinger et al.,
           Phys. Rev. Lett. 118, 190502 (2017)
           https://doi.org/10.1103/PhysRevLett.118.190502
           https://arxiv.org/abs/1702.01763

    :param rotation: the program or gate whose angle of rotation is to be estimated. Note that
        this program will be run through forest_benchmarking.compilation.basic_compile().
    :param change_of_basis: a matrix, gate, or program for the unitary change of basis
        transformation which maps the computational basis into the basis formed by eigenvectors
        of the rotation. The sign of the estimate will be determined by which computational basis
        states are mapped to which eigenvectors. Following the right-hand-rule convention,
        a rotation of RX(phi) for phi>0 about the +X axis should be paired with a change of basis
        maps |0> --> |+> and |1> --> |-> . This is achieved by the gate RY(pi/2, qubit).
    :param num_depths: the number of depths in the protocol described in [RPE]. A depth is the
        number of consecutive applications of the rotation in a single iteration. The maximum
        depth is 2**(num_depths-1)
    :param measure_qubits: the qubits whose angle of rotation, as a result of the action of
        the rotation program, RPE will attempt to estimate. These are the only qubits measured.
    :param prepare_and_post_select: is a bitstring used only in the multi-qubit case where one
        wishes to prepare the given qubits in the given classical state, and in analysis
        discard any results where those qubits are not observed in that state. Thus for a given
        prepare_and_post_select and a given measure_qubit being measured in the X or Y basis,
        the phase being estimated is the relative phase between the eigenvectors mapped to by the
        computational basis states consistent with the post_select_state and the equal
        superposition of the measure_qubit. For two qubits, one of the qubits may be assigned a
        bit, which will yield an estimate of one of the four possible phases typically
        estimated without post_select_state specified.
    :return: a dataframe populated with all of data necessary for the RPE protocol in [RPE]
    """
    if isinstance(rotation, Gate):
        rotation = Program(rotation)

    if isinstance(change_of_basis, Gate):
        change_of_basis = Program(change_of_basis)

    rotation_qubits = rotation.get_qubits()

    if measure_qubits is None:
        measure_qubits = rotation_qubits  # assume interest in qubits being rotated.
        # If you wish to measure multiple single qubit phases e.g. induced by cross-talk from the
        # operation of a gate on other qubits, consider creating multiple "dummy" experiments
        # that implement the identity and measure the qubits of interest. Subsequently run these
        # "dummies" simultaneously with an experiment whose rotation is the cross-talky program.

    qubits = rotation_qubits.union(measure_qubits)
    measure_qubits = sorted(measure_qubits)

    def df_dict():
        for exponent in range(num_depths):
            depth = 2**exponent
            for meas_dir in ["X", "Y"]:
                if len(measure_qubits) > 1:
                    # this is a >1q RPE experiment; the qubit being rotated and measured in X or
                    # Y direction need be indicated from among the available measure qubits.
                    for non_z_meas_qubit in measure_qubits:
                        if prepare_and_post_select and \
                                non_z_meas_qubit in prepare_and_post_select.keys():
                            # post-selected qubits are measured only in z-basis.
                            continue
                        yield {
                            "Qubits": qubits,
                            "Rotation": rotation,
                            "Depth": depth,
                            "Measure Direction": meas_dir,
                            "Measure Qubits": measure_qubits,
                            "Non-Z-Basis Meas Qubit": non_z_meas_qubit,
                            "Change of Basis": change_of_basis,
                        }
                else:
                    # standard 1q experiment, no need for Non-Z-Basis Meas Qubit
                    yield {
                        "Qubits": qubits,
                        "Rotation": rotation,
                        "Depth": depth,
                        "Measure Direction": meas_dir,
                        "Measure Qubits": measure_qubits,
                        "Change of Basis": change_of_basis,
                    }

    # TODO: Put dtypes on this DataFrame in the right way
    expt = DataFrame(df_dict())

    if prepare_and_post_select is not None:
        # construct and store a post-selection state assuming the order of measure_qubits
        state = [None] * len(measure_qubits)
        for idx, q in enumerate(measure_qubits):
            if q in prepare_and_post_select.keys():
                state[idx] = prepare_and_post_select[q]
        expt["Post Select State"] = [state for _ in range(expt.shape[0])]

    # change_of_basis is already specified as program, so add composed program column
    if isinstance(change_of_basis, Program):
        expt["Program"] = expt.apply(_make_prog_from_df, axis=1)

    return expt
Beispiel #24
0
 def run_program(self, prog: Program):
     n_qubits = len(prog.get_qubits())
     qvm = pyquil.get_qc("{}q-qvm".format(n_qubits))
     return qvm.run(prog)