Running the Quantum Volume Algorithm

This example walks through the steps of running the quantum volume algorithm on square matrices. It is intended to mirror Algorithm 1 of https://arxiv.org/pdf/1811.12926.pdf. In general, we will generate a model circuit, classically compute its Heavy Output Group, then run various samplers (currently, ideal and noisy simulators) to evaluate how often they generate Heavy results.


In [ ]:
# This cell sets up the parameters for the quantum volume algorithm.
# Feel free to mess with these!
import cirq

num_repetitions = 10  # This is supposed to be >= 100.
depths = range(2, 5)  # The depths and number of qubits
optimize = lambda circuit: cirq.google.optimized_for_xmon(
    circuit=circuit,
    new_device=cirq.google.Bristlecone)
device = cirq.google.Bristlecone

# Here is the important set-up: the samplers and their plot configurations.
# These are what will be run on the generated circuit, and then evaluated.
samplers = [{
    'label': 'Ideal simulation',
    'sampler': cirq.Simulator(),
    'marker': '+',
    'color': 'tab:green',
}, {
    'label': 'Noisy simulation',
    'sampler':
    cirq.DensityMatrixSimulator(noise=cirq.ConstantQubitNoiseModel(
        qubit_noise_gate=cirq.DepolarizingChannel(p=0.005))),
    'color': 'tab:red',
    'marker': 'o',
}]

print(f"Configuration: depths from {depths[0]} to {depths[-1]} with "
      f"{num_repetitions} runs of {len(samplers)} samplers")

In [ ]:
# This cell contains the business logic that actually runs the quantum volume algorithm with
# parameters specified in the previous cell.
from cirq.contrib import quantum_volume
from collections import defaultdict
import numpy as np

for sampler in samplers:
    sampler['probabilities'] = defaultdict(int)
    sampler['routed-probabilities'] = defaultdict(int)
    sampler['compiled-probabilities'] = defaultdict(int)

for depth in depths:
    num_qubits = depth  # Square matrix.
    print(f"Running simulation with {num_qubits} qubits and a depth of {depth}")
    for i in range(num_repetitions):
        print(f"    Repetition {i + 1} of {num_repetitions}")
        # Generate a model circuit and compute its heavy set.
        model_circuit = quantum_volume.generate_model_circuit(
            num_qubits, depth, random_state=np.random.RandomState())
        heavy_set = quantum_volume.compute_heavy_set(model_circuit)
        print(f"        Heavy Set: {heavy_set}")

        # Route and compile the model circuit.
        [routed_circuit,
         mapping] = quantum_volume.compile_circuit(model_circuit, device=device, routing_attempts=1)
        compiled_circuit = optimize(routed_circuit)

        # Run the given samplers over the model, compiled, and optimized circuits.
        for sampler in samplers:
            probability = quantum_volume.sample_heavy_set(
                model_circuit, heavy_set, sampler=sampler['sampler'])
            sampler['probabilities'][depth] += probability
            print(f"        {sampler['label']} HOG probability: {probability}")

            routed_probability = quantum_volume.sample_heavy_set(
                routed_circuit,
                heavy_set,
                sampler=sampler['sampler'],
                mapping=mapping)
            sampler['routed-probabilities'][depth] += routed_probability
            print(
                f"        {sampler['label']} HOG routed probability: {routed_probability}"
            )

            compiled_probability = quantum_volume.sample_heavy_set(
                compiled_circuit,
                heavy_set,
                sampler=sampler['sampler'],
                mapping=mapping)
            sampler['compiled-probabilities'][depth] += compiled_probability
            print(
                f"        {sampler['label']} HOG compiled probability: {compiled_probability}"
            )

    # Compute the average performance over the total number of runs.
    for sampler in samplers:
        sampler['probabilities'][depth] /= num_repetitions
        sampler['routed-probabilities'][depth] /= num_repetitions
        sampler['compiled-probabilities'][depth] /= num_repetitions
        print(f"    Average {sampler['label']} HOG probability: "
              f"{sampler['probabilities'][depth]}")
        print(f"    Average {sampler['label']} optimized HOG probability: "
              f"{sampler['routed-probabilities'][depth]}")
        print(f"    Average {sampler['label']} compiled HOG probability: "
              f"{sampler['compiled-probabilities'][depth]}")

In [ ]:
# Create a chart that is designed to look as similar as possible to
# Figure 2 in https://arxiv.org/pdf/1811.12926.pdf.

from matplotlib import pyplot as plt

fig, axs = plt.subplots()
for idx, sampler in enumerate(samplers):
    axs.scatter([d + idx / 10 for d in depths],
                sampler['probabilities'].values(),
                marker='+',
                c=f"{sampler['color']}",
                label=f"{sampler['label']}")
    axs.scatter([d + idx / 10 for d in depths],
                sampler['routed-probabilities'].values(),
                marker="o",
                c=f"{sampler['color']}",
                label=f"{sampler['label']} Routed")
    axs.scatter([d + idx / 10 for d in depths],
                sampler['compiled-probabilities'].values(),
                marker="s",
                c=f"{sampler['color']}",
                label=f"{sampler['label']} Compiled")

# Line markers for asymptotic ideal heavy output probability and the ideal
# Heavy Output Generation threshold.
axs.axhline((1 + np.log(2)) / 2,
            color='tab:green',
            label='Asymptotic ideal',
            linestyle='dashed')
axs.axhline(2 / 3, label='HOG threshold', color='k', linestyle='dotted')
# Making the plot look consistent.
axs.set_ybound(0.0, 1)
axs.xaxis.set_major_locator(plt.MultipleLocator(1))
axs.set_xlabel("width/depth of model circuit m=d")
axs.set_ylabel("est. heavy output probability h(d)")
fig.suptitle('Experimental data for square quantum volume circuits')
axs.legend(loc='best')