Пример #1
0
def estimate_joint_reset_confusion(qc: QuantumComputer, qubits: Sequence[int] = None,
                                   num_trials: int = 10, joint_group_size: int = 1,
                                   use_active_reset: bool = True, show_progress_bar: bool = False) \
                                    -> Dict[Tuple[int, ...], np.ndarray]:
    """
    Measures a reset 'confusion matrix' for all groups of size joint_group_size among the qubits.

    Specifically, for each possible joint_group_sized group among the qubits we perform a
    measurement for each bitstring on that group. The measurement proceeds as follows:

        -Repeatedly try to prepare the bitstring state on the qubits until success.
        -If use_active_reset is true (default) actively reset qubits to ground state; otherwise,
        wait the preset amount of time for qubits to decay to ground state.
        -Measure the state after the chosen reset method.

    Since reset should result in the all zeros state this 'confusion matrix' should ideally have
    all ones down the left-most column of the matrix. The entry at (row_idx, 0) thus represents
    the success probability of reset given that the pre-reset state is the binary representation
    of the number row_idx. WARNING: this method can be very slow

    :param qc: a quantum computer whose reset error you wish to characterize
    :param qubits: a list of accessible qubits on the qc you wish to characterize. Defaults to
        all qubits in qc.
    :param num_trials: number of repeated trials of reset after preparation of each bitstring on
        each joint group of qubits. Note: num_trials does not correspond to num_shots; a new
        program must be run in each trial, so running a group of n trials takes longer than would
        collecting the same number of shots output from a single program.
    :param joint_group_size: the size of each group; a square matrix with 2^joint_group_size
        number of rows/columns will be estimated for each group of qubits of the given size
        among the provided qubits.
    :param use_active_reset: dictates whether to actively reset qubits or else wait the
        pre-allotted amount of time for the qubits to decay to the ground state. Using active
        reset will allow for faster data collection.
    :param show_progress_bar: displays a progress bar via tqdm if true.
    :return: a dictionary whose keys are all possible joint_group_sized tuples that can be
        formed from the qubits. Each value is an estimated 2^group_size square matrix
        for the corresponding tuple of qubits. Each key is listed in order of increasing qubit
        number. The corresponding matrix has rows and columns indexed in increasing bitstring
        order, with most significant (leftmost) bit labeling the smallest qubit number. Each
        matrix row corresponds to the bitstring that was prepared before the reset, and each
        column corresponds to the bitstring measured after the reset.
    """
    # establish default as all operational qubits in qc
    if qubits is None:
        qubits = qc.qubits()

    qubits = sorted(qubits)

    groups = list(itertools.combinations(qubits, joint_group_size))
    confusion_matrices = {}

    total_num_rounds = len(groups) * 2**joint_group_size
    with tqdm(total=total_num_rounds, disable=not show_progress_bar) as pbar:
        for group in groups:
            reg_name = 'bitstr'
            # program prepares a bit string (specified by parameterization) and immediately measures
            prep_and_meas = parameterized_bitstring_prep(group, reg_name, append_measure=True)
            prep_executable = qc.compiler.native_quil_to_executable(prep_and_meas)

            matrix = np.zeros((2 ** joint_group_size, 2 ** joint_group_size))
            for row, bitstring in enumerate(itertools.product([0, 1], repeat=joint_group_size)):
                for _ in range(num_trials):

                    # try preparation at most 10 times.
                    for _ in range(10):
                        # prepare the given bitstring and measure
                        result = qc.run(prep_executable,
                                        memory_map={reg_name: [float(b) for b in bitstring]})

                        # if the preparation is successful, move on to reset.
                        if np.array_equal(result[0], bitstring):
                            break

                    # execute program that measures the post-reset state
                    if use_active_reset:
                        # program runs immediately after prep program and actively resets qubits
                        reset_measure_program = Program(RESET())
                    else:
                        # this program automatically waits pre-allotted time for qubits to decay
                        reset_measure_program = Program()
                    ro = reset_measure_program.declare('ro', memory_type='BIT',
                                                       memory_size=len(qubits))
                    for idx, qubit in enumerate(group):
                        reset_measure_program += MEASURE(qubit, ro[idx])
                    executable = qc.compiler.native_quil_to_executable(reset_measure_program)
                    results = qc.run(executable)

                    # update confusion matrix
                    for result in results:
                        base = np.array([2 ** i for i in reversed(range(joint_group_size))])
                        observed = np.sum(base * result)
                        matrix[row, observed] += 1 / num_trials

                # update the progress bar
                pbar.update(1)

            # store completed confusion matrix in dictionary
            confusion_matrices[group] = matrix

    return confusion_matrices
Пример #2
0
def estimate_joint_confusion_in_set(qc: QuantumComputer, qubits: Sequence[int] = None,
                                    num_shots: int = 1000, joint_group_size: int = 1,
                                    use_param_program: bool = True, use_active_reset=False,
                                    show_progress_bar: bool = False) \
                                    -> Dict[Tuple[int, ...], np.ndarray]:
    """
    Measures the joint readout confusion matrix for all groups of size group_size among the qubits.

    Simultaneous readout of multiple qubits may exhibit correlations in readout error.
    Measuring a single qubit confusion for each qubit of interest is therefore not sufficient to
    completely characterize readout error in general. This method estimates joint readout confusion
    matrices for groups of qubits, which involves preparing all 2^joint_group_size possible
    bitstrings on each of (len(qubits) choose joint_group_size) many groups. The joint_group_size
    specifies which order of correlation you wish to characterize, and a separate confusion
    matrix is estimated for every possible group of that size.

    For example, a joint_group_size of one will simply estimate a single-qubit 2x2 confusion
    matrix for each of the provided qubits, similar to len(qubits) calls to
    estimate_confusion_matrix. Meanwhile, a joint_group_size of two will yield estimates of the
    (len(qubits) choose 2) many 4x4 confusion matrices for each possible pair of qubits. When the
    joint_group_size is the same as len(qubits) a single confusion matrix is estimated,
    which requires 2^len(qubits) number of state preparations. Note that the maximum number of
    measurements occurs when 1 << joint_group_size << len(qubits), and this maximum is quite large.

    Because this method performs the measurement of exponentially many bitstrings on a particular
    group of qubits, use_param_program should result in an appreciable speed up for estimation
    of each matrix by looping over bitstrings at a lower level of the stack, rather than creating
    a new program for each bitstring. Use_active_reset should also speed up estimation, at the cost
    of introducing more error and potentially complicating interpretation of results.

    :param qc: a quantum computer whose readout error you wish to characterize
    :param qubits: a list of accessible qubits on the qc you wish to characterize. Defaults to
        all qubits in qc.
    :param num_shots: number of shots in measurement of each bit string on each joint group of
        qubits.
    :param joint_group_size: the size of each group; a joint confusion matrix with
        2^joint_group_size number of rows/columns will be estimated for each group of qubits of
        the given size within the qubit set.
    :param use_param_program: dictates whether to use a parameterized program to measure out
        each bitstring. If set to default of True, this routine should execute faster on a
        QPU. Note that the parameterized option does not execute a no-op when measuring 0.
    :param use_active_reset: if true, all qubits in qc (not just provided qubits) will be actively
        reset to 0 state at the start of each bitstring measurement. This option is intended as a
        potential speed up, but may complicate interpretation of the estimated confusion
        matrices. The method estimate_joint_active_reset_confusion separately characterizes
        active reset.
    :param show_progress_bar: displays a progress bar via tqdm if true.
    :return: a dictionary whose keys are all possible joint_group_sized tuples that can be
        formed from the qubits. Each value is an estimated 2^group_size square confusion matrix
        for the corresponding tuple of qubits. Each key is listed in order of increasing qubit
        number. The corresponding matrix has rows and columns indexed in increasing bitstring
        order, with most significant (leftmost) bit labeling the smallest qubit number.
    """
    # establish default as all operational qubits in qc
    if qubits is None:
        qubits = qc.qubits()

    qubits = sorted(qubits)

    groups = list(itertools.combinations(qubits, joint_group_size))
    confusion_matrices = {}

    total_num_rounds = len(groups) * 2**joint_group_size
    with tqdm(total=total_num_rounds, disable=not show_progress_bar) as pbar:
        for group in groups:

            # initialize program to optionally actively reset. Active reset will provide a speed up
            # but may make the estimate harder to interpret due to errors in the reset.
            program_start = Program()
            if use_active_reset:
                program_start += RESET()

            executable = None  # will be re-assigned to either compiled param or non-param program

            reg_name = 'bitstr'
            # generate one parameterized program now, or later create a program for each bitstring
            if use_param_program:
                # generate parameterized program for this group and append to start
                prep_and_meas = parameterized_bitstring_prep(group, reg_name, append_measure=True)
                param_program = program_start + prep_and_meas
                param_program.wrap_in_numshots_loop(shots=num_shots)
                executable = qc.compiler.native_quil_to_executable(param_program)

            matrix = np.zeros((2 ** joint_group_size, 2 ** joint_group_size))
            for row, bitstring in enumerate(itertools.product([0, 1], repeat=joint_group_size)):

                if use_param_program:
                    # specify bitstring in parameterization at run-time
                    results = qc.run(executable,
                                     memory_map={reg_name: [float(b) for b in bitstring]})
                else:
                    # generate program that measures given bitstring on group, and append to start
                    bitstring_program = program_start + bitstring_prep(group, bitstring,
                                                                       append_measure=True)
                    bitstring_program.wrap_in_numshots_loop(shots=num_shots)
                    executable = qc.compiler.native_quil_to_executable(bitstring_program)
                    results = qc.run(executable)

                # update confusion matrix
                for result in results:
                    base = np.array([2 ** i for i in reversed(range(joint_group_size))])
                    observed = np.sum(base * result)
                    matrix[row, observed] += 1 / num_shots

                # update the progress bar
                pbar.update(1)

            # store completed confusion matrix in dictionary
            confusion_matrices[group] = matrix

    return confusion_matrices
def adder(num_a: Sequence[int],
          num_b: Sequence[int],
          register_a: Sequence[int],
          register_b: Sequence[int],
          carry_ancilla: int,
          z_ancilla: int,
          in_x_basis: bool = False,
          use_param_program: bool = False) -> Program:
    """
    Produces a program implementing reversible adding on a quantum computer to compute a + b.

    This implementation is based on [CDKM96]_, which is easy to implement, if not the most
    efficient. Each register of qubit labels should be provided such that the first qubit in
    each register is expected to carry the least significant bit of the respective number. This
    method also requires two extra ancilla, one initialized to 0 that acts as a dummy initial
    carry bit and another (which also probably ought be initialized to 0) that stores the most
    significant bit of the addition (should there be a final carry). The most straightforward
    ordering of the registers and two ancilla for adding n-bit numbers follows the pattern::

        carry_ancilla
        b_0
        a_0
        ...
        b_j
        a_j
        ...
        b_n
        a_n
        z_ancilla

    With this layout, all gates in the circuit act on sets of three adjacent qubits. Such a
    layout is provided by calling get_qubit_registers_for_adder on the quantum resource. Note
    that even with this layout some of the gates used to implement the circuit may not be native.
    In particular there are CCNOT gates which must be decomposed and CNOT(q1, q3) gates acting on
    potentially non-adjacenct qubits (the layout only ensures q2 is adjacent to both q1 and q3).

    The output of the circuit falls on the qubits initially labeled by the b bits (and z_ancilla).

    The default option is to compute the addition in the computational (aka Z) basis. By setting
    in_x_basis true, the gates :func:`primitives.CNOT_X_basis` and :func:`primitives.CCNOT_X_basis`
    will replace CNOT and CCNOT so that the computation happens in the X basis.

    .. [CDKM96] "A new quantum ripple-carry addition circuit"
        S. Cuccaro, T. Draper, s. Kutin, D. Moulton
        https://arxiv.org/abs/quant-ph/0410184

    :param num_a: the bitstring representation of the number a with least significant bit last
    :param num_b: the bitstring representation of the number b with least significant bit last
    :param register_a: list of qubit labels for register a, with least significant bit labeled first
    :param register_b: list of qubit labels for register b, with least significant bit labeled first
    :param carry_ancilla: qubit labeling a zero-initialized qubit, ideally adjacent to b_0
    :param z_ancilla: qubit label, a zero-initialized qubit, ideally adjacent to register_a[-1]
    :param in_x_basis: if true, prepare the bitstring-representation of the numbers in the x basis
        and subsequently performs all addition logic in the x basis.
    :param use_param_program: if true, the input num_a and num_b should be arrays of the proper
        length, but their contents will be disregarded. Instead, the program returned will be
        parameterized and the input bitstrings to add must be specified at run time.
    :return: pyQuil program that implements the addition a+b, with output falling on the qubits
        formerly storing the input b. The output of a measurement will list the lsb as the last bit.
    """
    if len(num_a) != len(num_b):
        raise ValueError("Numbers being added must be equal length bitstrings")

    # First, generate a set preparation program in the desired basis.
    prog = Program(Pragma('PRESERVE_BLOCK'))
    if use_param_program:
        input_register = register_a + register_b
        prog += parameterized_bitstring_prep(input_register[::-1],
                                             REG_NAME,
                                             in_x_basis=in_x_basis)
    else:
        prog += bitstring_prep(register_a, num_a[::-1], in_x_basis=in_x_basis)
        prog += bitstring_prep(register_b, num_b[::-1], in_x_basis=in_x_basis)

    if in_x_basis:
        prog += [H(carry_ancilla), H(z_ancilla)]

    # preparation complete; end the preserve block
    prog += Pragma("END_PRESERVE_BLOCK")

    prog_to_rev = Program()
    current_carry_label = carry_ancilla
    for (a, b) in zip(register_a, register_b):
        prog += majority_gate(a, b, current_carry_label, in_x_basis)
        prog_to_rev += unmajority_add_gate(a, b, current_carry_label,
                                           in_x_basis).dagger()
        current_carry_label = a

    undo_and_add_prog = prog_to_rev.dagger()
    if in_x_basis:
        prog += CNOT_X_basis(register_a[-1], z_ancilla)
        # need to switch back to computational (z) basis before measuring
        for qubit in register_b:  # answer lays on the b qubit register
            undo_and_add_prog.inst(H(qubit))
        undo_and_add_prog.inst(H(z_ancilla))
    else:
        prog += CNOT(register_a[-1], z_ancilla)
    prog += undo_and_add_prog

    ro = prog.declare('ro', memory_type='BIT', memory_size=len(register_b) + 1)
    for idx, qubit in enumerate(register_b):
        prog += MEASURE(qubit, ro[len(register_b) - idx])
    prog += MEASURE(z_ancilla, ro[0])

    return prog