def get_success_probabilities_from_results(results: Sequence[Sequence[Sequence[int]]]) \
        -> Sequence[float]:
    """
    Get the probability of a successful addition for each possible pair of two n_bit summands
    from the results output by get_n_bit_adder_results

    :param results: a list of results output from a call to get_n_bit_adder_results
    :return: the success probability for the summation of each possible pair of n_bit summands
    """
    num_shots = len(results[0])
    n_bits = len(results[0][0]) - 1

    probabilities = []
    # loop over all binary strings of length n_bits
    for result, bits in zip(results, all_bitstrings(2 * n_bits)):
        # Input nums are written from (MSB .... LSB) = (a_n, ..., a_1, a_0)
        num_a = bit_array_to_int(bits[:n_bits])
        num_b = bit_array_to_int(bits[n_bits:])

        # add the numbers
        ans = num_a + num_b
        ans_bits = int_to_bit_array(ans, n_bits + 1)

        # a success occurs if a shot matches the expected ans bit for bit
        probability = 0
        for shot in result:
            if np.array_equal(ans_bits, shot):
                probability += 1. / num_shots
        probabilities.append(probability)

    return probabilities
def get_error_hamming_distributions_from_results(results: Sequence[Sequence[Sequence[int]]]) \
        -> Sequence[Sequence[float]]:
    """
    Get the distribution of the hamming weight of the error vector (number of bits flipped
    between output and expected answer) for each possible pair of two n_bit summands using
    results output by get_n_bit_adder_results

    :param results: a list of results output from a call to get_n_bit_adder_results
    :return: the relative frequency of observing each hamming weight, 0 to n_bits+1, for the error
        that occurred when adding each pair of two n_bit summands
    """
    num_shots = len(results[0])
    n_bits = len(results[0][0]) - 1

    hamming_wt_distrs = []
    # loop over all binary strings of length n_bits
    for result, bits in zip(results, all_bitstrings(2 * n_bits)):
        # Input nums are written from (MSB .... LSB) = (a_n, ..., a_1, a_0)
        num_a = bit_array_to_int(bits[:n_bits])
        num_b = bit_array_to_int(bits[n_bits:])

        # add the numbers
        ans = num_a + num_b
        ans_bits = int_to_bit_array(ans, n_bits + 1)

        # record the fraction of shots that resulted in an error of the given weight
        hamming_wt_distr = [0. for _ in range(len(ans_bits) + 1)]
        for shot in result:
            # multiply relative hamming distance by the length of the output for the weight
            wt = len(ans_bits) * hamming(ans_bits, shot)
            hamming_wt_distr[int(wt)] += 1. / num_shots

        hamming_wt_distrs.append(hamming_wt_distr)

    return hamming_wt_distrs
예제 #3
0
def sample_rand_circuits_for_heavy_out(
        qc: QuantumComputer,
        qubits: Sequence[int],
        depth: int,
        program_generator: Callable[
            [QuantumComputer, Sequence[int], Sequence[np.ndarray], np.ndarray],
            Program],
        num_circuits: int = 100,
        num_shots: int = 1000,
        show_progress_bar: bool = False) -> int:
    """
    This method performs the bulk of the work in the quantum volume measurement.

    For the given depth, num_circuits many random model circuits are generated, the heavy outputs
    are determined from the ideal output distribution of each circuit, and a native quil
    implementation of the model circuit output by the program generator is run on the qc. The total
    number of sampled heavy outputs is returned.

    :param qc: the quantum resource that will implement the PyQuil program for each model circuit
    :param qubits: the qubits available in the qc for the program_generator to use.
    :param depth: the depth (and width in num of qubits) of the model circuits
    :param program_generator: a method which takes an abstract description of a model circuit and
        returns a native quil program that implements that circuit. See measure_quantum_volume
        docstring for specifics.
    :param num_circuits: the number of random model circuits to sample at this depth; should be >100
    :param num_shots: the number of shots to sample from each model circuit
    :param show_progress_bar: displays a progress bar via tqdm if true.
    :return: the number of heavy outputs sampled among all circuits generated for this depth
    """
    wfn_sim = NumpyWavefunctionSimulator(depth)

    num_heavy = 0
    # display progress bar using tqdm
    for _ in tqdm(range(num_circuits), disable=not show_progress_bar):

        permutations, gates = generate_abstract_qv_circuit(depth)

        # generate a PyQuil program in native quil that implements the model circuit
        # The program should measure the output qubits in the order that is consistent with the
        # comparison of the bitstring results to the heavy outputs given by collect_heavy_outputs
        program = program_generator(qc, qubits, permutations, gates)

        # run the program num_shots many times
        program.wrap_in_numshots_loop(num_shots)
        executable = qc.compiler.native_quil_to_executable(program)
        results = qc.run(executable)

        # classically simulate model circuit represented by the perms and gates for heavy outputs
        heavy_outputs = collect_heavy_outputs(wfn_sim, permutations, gates)

        # determine if each result bitstring is a heavy output, as determined from simulation
        for result in results:
            # convert result to int for comparison with heavy outputs.
            output = bit_array_to_int(result)
            if output in heavy_outputs:
                num_heavy += 1

    return num_heavy
 def count(hh, res):
     num_heavy = 0
     # determine if each result bitstring is a heavy output, as determined from simulation
     for result in res:
         # convert result to int for comparison with heavy outputs.
         output = bit_array_to_int(result)
         if output in hh:
             num_heavy += 1
     return num_heavy
예제 #5
0
def count_heavy_hitters_sampled(
        qc_results: Iterator[np.ndarray],
        heavy_hitters: Iterator[List[int]]) -> Iterator[int]:
    """
    Simple helper to count the number of heavy hitters sampled given the sampled results for a
    number of circuits along with the the actual heavy hitters for each circuit.

    :param qc_results: results from running each circuit on a quantum computer.
    :param heavy_hitters: the heavy hitters for each circuit (presumably calculated through
        simulating the circuit classically)
    :return: the number of samples which were heavy for each circuit.
    """
    for results, hh_list in zip(qc_results, heavy_hitters):
        num_heavy = 0
        # determine if each result bitstring is a heavy output, as determined from simulation
        for result in results:
            # convert result to int for comparison with heavy outputs.
            output = bit_array_to_int(result)
            if output in hh_list:
                num_heavy += 1
        yield num_heavy