Dynamic Bernstein–Vazirani

Here we will demonstrate correcting mid-circuit measurements using the dynamic version of the Bernstein-Vazirani algorithm.

Frontmatter

[1]:
import numpy as np

from qiskit import *
from qiskit_ibm_runtime.fake_provider import FakeKolkataV2

import mthree

import matplotlib.pyplot as plt
plt.style.use('quantum-light')

Set target noisy simulator

[2]:
backend = FakeKolkataV2()

Circuit generation function

[3]:
def dynamic_bv(bitstring):
    """Create a Bernstein-Vazirani circuit from a given bitstring.

    Parameters:
        bitstring (str): A bitstring.

    Returns:
        QuantumCircuit: Output circuit.
    """
    qc = QuantumCircuit(2, len(bitstring))

    # Prepare the |-x> state on target qubit
    qc.x(1)
    qc.h(1)

    # For each bit (0 or 1) build a simple circuit block
    for idx, bit in enumerate(bitstring[::-1]):

        # Initial H gate on control
        qc.h(0)
        # If bit=1, do a CNOT gate
        if int(bit):
            qc.cx(0, 1)
        # Final H gate to convert phase to computational-basis
        qc.h(0)
        # Measure
        qc.measure(0, idx)

        # If not at the final bit, recycle and reset qubits
        if idx != (len(bitstring)-1):
            # Reset control qubit for reuse
            qc.reset(0)
            # reset target qubit to minimize dephasing
            qc.reset(1)
            # Prepare the |-x> state on target qubit again
            qc.x(1)
            qc.h(1)

    return qc

Problem setup

First we select a range of bit-string lengths to generate. We then use this range to make all-ones bit-string circuits of those lengths.

[4]:
bit_range = range(2, 31)

circs = [dynamic_bv('1'*N) for N in bit_range]

Next, we transpile the circuits for our target backend (simulator in this case):

[5]:
trans_circs = transpile(circs, backend, optimization_level=3)

Next, we determine which qubits are measured to which classical bits using the final_measurement_mapping utility:

[6]:
mappings = mthree.utils.final_measurement_mapping(trans_circs)
# Show a few of the mappings
mappings[:3]
[6]:
[{0: 25, 1: 25}, {0: 25, 1: 25, 2: 25}, {0: 25, 1: 25, 2: 25, 3: 25}]

Lets draw the 0th circuit to verify that the mapping is indeed correct:

[7]:
trans_circs[0].draw('mpl', idle_wires=False)
[7]:
../_images/tutorials_04_dynamic_bv_14_0.png

Run experiment and mitigate

Here we execute the dynamic BV ciruits at 10,000 shots each.

[8]:
shots = int(1e4)
counts = backend.run(trans_circs, shots=shots).result().get_counts()

Next we follow the usual M3 receipe to mitigate the counts. Note that the mappings have all the necessary information for correcting mid-circuit measurements.

[9]:
mit = mthree.M3Mitigation(backend)
[10]:
mit.cals_from_system(mappings)
[10]:
[<qiskit_aer.jobs.aerjob.AerJob at 0x7f8662414160>]
[11]:
quasis = mit.apply_correction(counts, mappings)

Because we generated all-ones bit-strings, our success criteria is the probability of being found in that state. We can extract this probabilty from both the raw counts and the mitigated quasi-distributions

[12]:
count_probs = [counts[idx].get('1'*num_bits)/shots \
                for idx, num_bits in enumerate(bit_range)]

quasi_probs = [quasis[idx].get('1'*num_bits) \
               for idx, num_bits in enumerate(bit_range)]

Plot the results

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