Mitigating sampling problems

There are many cases where one is interested in computing the most probable bitstring in the output distribution from a circuit. Although, M3 returns quasi-probabilities, the most likely bit-string is something that is always positive, and we can mitigate sampling problems of this form. Below is a simple example for Bernstein–Vazirani (BV) circuits.

Frontmatter

[1]:
from qiskit import *
from qiskit_ibm_runtime.fake_provider import FakeKolkataV2

import mthree

import matplotlib.pyplot as plt
plt.style.use('quantum-light')
[2]:
backend = FakeKolkataV2()

Setup experiment

Here we generate BV circuits for all-ones bit-strings of various lengths, and transpile them against our target backend. We also create a (not-rigourous) function that finds the most likely bit-string from a distribution.

[3]:
def bv_ones_circs(N):
    """Create a Bernstein–Vazirani (BV) circuit of width N

    Here the bit-string length is N-1

    Parameters:
        N (int): Number of qubits in circuit

    Returns:
        QuantumCircuit: BV circuit
    """
    qc = QuantumCircuit(N, N-1)
    qc.x(N-1)
    qc.h(range(N))
    qc.cx(range(N-1), N-1)
    qc.h(range(N-1))
    qc.barrier()
    qc.measure(range(N-1), range(N-1))
    return qc


def most_likely_bitstring(dist):
    """Computes the most likely bit-string from a given distribution

    Parameters:
        dist (dict): Input distribution

    Returns:
        str: Most likely bit-string
        int, float: Most likely value
    """
    sorted_dist = dict(sorted(dist.items(), key=lambda item: item[1], reverse=True))
    key = next(iter(sorted_dist.keys()))
    val = next(iter(sorted_dist.values()))
    return key, val
[4]:
bit_range = range(2, 13)

circs = [bv_ones_circs(N+1) for N in bit_range]
[5]:
trans_circs = transpile(circs, backend, optimization_level=3)

Perform experiment

We generate the raw data for the circuits, and then perform our mitigation in the usual way.

[6]:
shots = 8192
counts = backend.run(trans_circs, shots=shots).result().get_counts()

Get the success probability, validating that our returned bit-string is what we expect.

[7]:
raw_success_probs = []

for idx, num_bits in enumerate(bit_range):
    max_bitstring, max_value = most_likely_bitstring(counts[idx])
    if max_bitstring != '1'*num_bits:
        raise Exception('Returned wrong bit-string')
    raw_success_probs.append(max_value/shots)
[8]:
mit = mthree.M3Mitigation(backend)
[9]:
mappings = mthree.utils.final_measurement_mapping(trans_circs)
[10]:
mit.cals_from_system(mappings)
[10]:
[<qiskit_aer.jobs.aerjob.AerJob at 0x7fa15d9cecb0>]
[11]:
quasis = mit.apply_correction(counts, mappings)

Get the mitigated success probability, validating that our returned bit-string is what we expect.

[12]:
mit_success_probs = []

for idx, num_bits in enumerate(bit_range):
    max_bitstring, max_value = most_likely_bitstring(quasis[idx])
    if max_bitstring != '1'*num_bits:
        raise Exception('Returned wrong bit-string')
    mit_success_probs.append(max_value)

Plot results

Here we plot the results

[13]:
fig, ax = plt.subplots()
ax.plot(bit_range, raw_success_probs, label='Raw')
ax.plot(bit_range, mit_success_probs, label='Mitigated')
ax.set_ylabel('Success probability')
ax.set_xlabel('Bit-string length')
ax.legend();
_images/sampling_19_0.png
[ ]: