def get_qubit_registers_for_adder(qc: QuantumComputer, num_length: int,
                                  qubits: Optional[Sequence[int]] = None)\
        -> Tuple[Sequence[int], Sequence[int], int, int]:
    """
    Searches for a layout among the given qubits for the two n-bit registers and two additional
    ancilla that matches the simple layout given in figure 4 of [CDKM96]_.

    This method ignores any considerations of physical characteristics of the qc aside from the
    qubit layout. An error is thrown if the appropriate layout is not found.

    :param qc: the quantum resource on which an adder program will be executed.
    :param num_length: the length of the bitstring representation of one summand
    :param qubits: the available qubits on which to run the adder program.
    :return: the necessary registers and ancilla labels for implementing an adder
        program to add the numbers a and b. The output can be passed directly to :func:`adder`
    """
    if qubits is None:
        unavailable = []  # assume this means all qubits in qc are available
    else:
        unavailable = [qubit for qubit in qc.qubits() if qubit not in qubits]

    graph = qc.qubit_topology().copy()
    for qubit in unavailable:
        graph.remove_node(qubit)

    # network x only provides subgraph isomorphism, but we want a subgraph monomorphism, i.e. we
    # specifically want to match the edges desired_layout with some subgraph of graph. To
    # accomplish this, we swap the nodes and edges of graph by making a line graph.
    line_graph = nx.line_graph(graph)

    # We want a path of n nodes, which has n-1 edges. Since we are matching edges of graph with
    # nodes of layout we make a layout of n-1 nodes.
    num_desired_nodes = 2 * num_length + 2
    desired_layout = nx.path_graph(num_desired_nodes - 1)

    g_matcher = nx.algorithms.isomorphism.GraphMatcher(line_graph,
                                                       desired_layout)

    try:
        # pick out a subgraph isomorphic to the desired_layout if one exists
        # this is an isomorphic mapping from edges in graph (equivalently nodes of line_graph) to
        # nodes in desired_layout (equivalently edges of a path graph with one more node)
        edge_iso = next(g_matcher.subgraph_isomorphisms_iter())
    except IndexError:
        raise Exception(
            "An appropriate layout for the qubits could not be found among the "
            "provided qubits.")

    # pick out the edges of the isomorphism from the original graph
    subgraph = nx.Graph(graph.edge_subgraph(edge_iso.keys()))

    # pick out an endpoint of our path to start the assignment
    start_node = -1
    for node in subgraph.nodes:
        if subgraph.degree(node) == 1:  # found an endpoint
            start_node = node
            break

    return assign_registers_to_line_or_cycle(start_node, subgraph, num_length)
def process_characterisation(qc: QuantumComputer) -> dict:
    """Convert a :py:class:`pyquil.api.QuantumComputer` to a dictionary containing
    Rigetti device Characteristics

    :param qc: A quantum computer to be converted
    :type qc: QuantumComputer
    :return: A dictionary containing Rigetti device characteristics
    """
    specs = qc.device.get_specs()

    coupling_map = [[n, ni]
                    for n, neigh_dict in qc.qubit_topology().adjacency()
                    for ni, _ in neigh_dict.items()]

    node_ers_dict = {}
    link_ers_dict = {}
    t1_times_dict = {}
    t2_times_dict = {}

    device_node_fidelities = specs.f1QRBs()  # type: ignore
    device_link_fidelities = specs.fCZs()  # type: ignore
    device_fROs = specs.fROs()  # type: ignore
    device_t1s = specs.T1s()  # type: ignore
    device_t2s = specs.T2s()  # type: ignore

    for index in qc.qubits():
        error_cont = QubitErrorContainer({OpType.Rx, OpType.Rz})
        error_cont.add_readout(1 - device_fROs[index])  # type: ignore
        t1_times_dict[index] = device_t1s[index]
        t2_times_dict[index] = device_t2s[index]
        error_cont.add_error(
            (OpType.Rx, 1 - device_node_fidelities[index]))  # type: ignore
        # Rigetti use frame changes for Rz, so they effectively have no error.
        node_ers_dict[Node(index)] = error_cont

    for (a, b), fid in device_link_fidelities.items():
        error_cont = QubitErrorContainer({OpType.CZ})
        error_cont.add_error((OpType.CZ, 1 - fid))  # type: ignore
        link_ers_dict[(Node(a), Node(b))] = error_cont
        link_ers_dict[(Node(b), Node(a))] = error_cont

    arc = Architecture(coupling_map)

    characterisation = dict()
    characterisation["NodeErrors"] = node_ers_dict
    characterisation["EdgeErrors"] = link_ers_dict
    characterisation["Architecture"] = arc
    characterisation["t1times"] = t1_times_dict
    characterisation["t2times"] = t2_times_dict

    return characterisation
Пример #3
0
def add_programs_to_dataframe(
    df: DataFrame,
    qc: QuantumComputer,
    qubits_at_depth: Dict[int, Sequence[int]] = None,
    program_generator: Callable[
        [QuantumComputer, Sequence[int], np.ndarray, np.ndarray],
        Program] = _naive_program_generator
) -> DataFrame:
    """
    Passes the abstract circuit description in each row of the dataframe df along to the supplied
    program_generator which yields a program that can be run on the available
    qubits_at_depth[depth] on the given qc resource.

    :param df: a dataframe populated with abstract descriptions of model circuits, i.e. a df
        returned by a call to generate_quantum_volume_experiments.
    :param qc: the quantum resource on which each output program will be run.
    :param qubits_at_depth: the qubits of the qc available for use at each depth, default all
        qubits in the qc for each depth. Any subset of these may actually be used by the program.
    :param program_generator: a method which uses the given qc, its available qubits, and an
        abstract description of the model circuit to produce a PyQuil program implementing the
        circuit using only native gates and the given qubits. This program must respect the
        topology of the qc induced by the given qubits. The default _naive_program_generator uses
        the qc's compiler to achieve this result.
    :return: a copy of df with a new "Program" column populated with native PyQuil programs that
        implement the circuit in "Abstract Ckt" on the qc using a subset of the qubits specified
        as available for the given depth. The used qubits are also recorded in a "Qubits" column.
        Note that although the abstract circuit has depth=width, for the program width >= depth.
    """
    new_df = df.copy()

    depths = new_df["Depth"].values
    circuits = new_df["Abstract Ckt"].values

    if qubits_at_depth is None:
        all_qubits = qc.qubits(
        )  # by default the program can act on any qubit in the computer
        qubits = [all_qubits for _ in circuits]
    else:
        qubits = [qubits_at_depth[depth] for depth in depths]

    programs = [
        program_generator(qc, qbits, *ckt)
        for qbits, ckt in zip(qubits, circuits)
    ]
    new_df["Program"] = Series(programs)

    # these are the qubits actually used in the program, a subset of qubits_at_depth[depth]
    new_df["Qubits"] = Series([program.get_qubits() for program in programs])

    return new_df
Пример #4
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:
            # program prepares a bit string (specified by parameterization) and immediately measures
            prep_program = _readout_group_parameterized_bitstring(group)
            prep_executable = qc.compiler.native_quil_to_executable(
                prep_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)):
                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={
                                            'target':
                                            [bit * pi for bit 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
Пример #5
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

            # 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
                param_program = program_start + _readout_group_parameterized_bitstring(
                    group)
                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={'target': [bit * pi for bit in bitstring]})
                else:
                    # generate program that measures given bitstring on group, and append to start
                    bitstring_program = program_start + _readout_group_bitstring(
                        group, bitstring)
                    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
Пример #6
0
def measure_quantum_volume(qc: QuantumComputer, qubits: Sequence[int] = None,
                           program_generator: Callable[[QuantumComputer, Sequence[int],
                                                        Sequence[np.ndarray], np.ndarray],
                                                       Program] = _naive_program_generator,
                           num_circuits: int = 100, num_shots: int = 1000,
                           depths: np.ndarray = None, achievable_threshold: float = 2 / 3,
                           stop_when_fail: bool = True, show_progress_bar: bool = False) \
        -> Dict[int, Tuple[float, float]]:
    """
    Measures the quantum volume of a quantum resource, as described in [QVol]_.

    By default this method scans increasing depths from 2 to len(qubits) and tests whether the qc
    can adequately implement random model circuits on depth-many qubits such that the given
    depth is 'achieved'. A model circuit depth is achieved if the sample distribution for a
    sample of num_circuits many randomly generated model circuits of the given depth sufficiently
    matches the ideal distribution of that circuit (See Eq. 6  of [QVol]_). The frequency of
    sampling 'heavy-outputs' is used as a measure of closeness of the circuit distributions. This
    estimated frequency (across all sampled circuits) is reported for each depth along with a
    bool which indicates whether that depth was achieved. The logarithm of the quantum volume is by
    definition the largest achievable depth of the circuit; see
    :func:`extract_quantum_volume_from_results` for obtaining the quantum volume from the results
    returned by this method.

    .. [QVol] Validating quantum computers using randomized model circuits.
           Cross et al.
           arXiv:1811.12926v1  (2018).
           https://arxiv.org/abs/1811.12926

    :param qc: the quantum resource whose volume you wish to measure
    :param qubits: available qubits on which to act during measurement. Default all qubits in qc.
    :param program_generator: a method which

        1) takes in a quantum computer, the qubits on that
            computer available for use, a series of sequences representing the qubit permutations
            in a model circuit, an array of matrices representing the 2q gates in the model circuit
        2) outputs a native quil program that implements the circuit and measures the appropriate
            qubits in the order implicitly dictated by the model circuit representation created in
            sample_rand_circuits_for_heavy_out.

        The default option simply picks the smallest qubit labels and lets the compiler do the rest.
    :param num_circuits: number of unique random circuits that will be sampled.
    :param num_shots: number of shots for each circuit sampled.
    :param depths: the circuit depths to scan over. Defaults to all depths from 2 to len(qubits)
    :param achievable_threshold: threshold at which a depth is considered 'achieved'. Eq. 6 of
        [QVol]_ defines this to be the default of 2/3. To be considered achievable, the estimated
        probability of sampling a heavy output at the given depth must be large enough such that
        the one-sided confidence interval of this estimate is greater than the given threshold.
    :param stop_when_fail: if true, the measurement will stop after the first un-achievable depth
    :param show_progress_bar: displays a progress bar for each depth if true.
    :return: dict with key depth: (prob_sample_heavy, ons_sided_conf_interval) gives both the
        estimated probability of sampling a heavy output at each depth and the 2-sigma lower
        bound on this estimate; a depth qualifies as being achievable only if this lower bound
        exceeds the threshold, defined in [QVol]_ to be 2/3
    """
    if num_circuits < 100:
        warnings.warn(
            "The number of random circuits ran ought to be greater than 100 for results "
            "to be valid.")
    if qubits is None:
        qubits = qc.qubits()

    if depths is None:
        depths = np.arange(2, len(qubits) + 1)

    results = {}
    for depth in depths:
        log.info("Starting depth {}".format(depth))

        # Use the program generator to implement random model circuits for this depth and compare
        # the outputs to the ideal simulations; get the count of the total number of heavy outputs
        num_heavy = sample_rand_circuits_for_heavy_out(qc, qubits, depth,
                                                       program_generator,
                                                       num_circuits, num_shots,
                                                       show_progress_bar)

        prob_sample_heavy, one_sided_conf_intrvl = calculate_prob_est_and_err(
            num_heavy, num_circuits, num_shots)

        # prob of sampling heavy output must be large enough such that the one-sided confidence
        # interval is larger than the threshold
        is_achievable = one_sided_conf_intrvl > achievable_threshold

        results[depth] = (prob_sample_heavy, one_sided_conf_intrvl)

        if stop_when_fail and not is_achievable:
            break

    return results