Bernstein-Vazirani Algorithm

The Bernstein–Vazirani algorithm was invented by Ethan Bernstein and Umesh Vazirani in 1997. The Bernstein–Vazirani problem is defined as follows:

Problem Definition

Given a function \(f∶\left\{0,1\right\}^n\mapsto\left\{0,1\right\}\), with the oracle function \(f(x)=a\cdot x\), where a is a constant vector of \(\left\{ 0,1 \right\}^n\), and \(a\cdot x\) is a scalar product of a and`x`. The goal of the oracle is to find the hidden bit string a.

A classical solution to this problem requires n steps. We can retrieve the hidden string \(a(a_0 a_1 a_2\ldots a_{n-1} )\) by querying the oracle with a value of x, such that each query returns one of the bits in the hidden string. For example:

\[\begin{split}f(0001) = a_0 \\ f(0010) = a_1 \\ f(0100) = a_2 \\ f(1000) = a_3\end{split}\]

After querying all the bits, we can stitch the results and obtain the hidden string \((a_0 a_1 a_2 a_3)\). There are a few ways to solve this problem on a quantum computer, and we can get the answer in one step. The block diagram [17] for the solution to the Bernstein-Vazirani oracle is given below.

Bernstein Vazirani Block Diagram.

We can implement the oracle function as a set of CNOT gates, with the ancilla qubit as the target and the data register qubits corresponding to the bit value of the hidden string a as the control qubits. The following figure [18] illustrates this for a hidden string \(10 =1010b\).

Bernstein Vazirani Quantum Circuit.

For a detailed step-by-step math of this algorithm, see [12]. The source code for this oracle is outlined in the following sections.

Note

Be sure to use your API token and your account name.

Step 1. Import the required modules and obtain the backend

import QuantumRingsLib
from QuantumRingsLib import QuantumRegister, AncillaRegister, ClassicalRegister, QuantumCircuit
from QuantumRingsLib import QuantumRingsProvider
from QuantumRingsLib import job_monitor
from QuantumRingsLib import JobStatus
from matplotlib import pyplot as plt
import numpy as np

provider = QuantumRingsProvider(token =<YOUR_TOKEN_HERE>, name=<YOUR_ACCOUNT_NAME_HERE>)
backend = provider.get_backend("scarlet_quantum_rings")
shots = 100

provider.active_account()

Step 2. Define the core methods

def bv_func (qc, hiddenstring):

    # set the ancilla qubit to state |1>
    qc.x(qc.num_qubits-1)

    # Set all qubits to a superposition state.
    for i in range (qc.num_qubits):
        qc.h(i)

    qc.barrier()

    # implement the hidden string - the oracle function
    for i in range (qc.num_qubits - 1):
        if ( 0!= ( hiddenstring & (1 << i))):
            qc.cx(i, qc.num_qubits-1)

    qc.barrier()

    # Apply H gates one more time
    for i in range (qc.num_qubits-1):
        qc.h(i)

    qc.barrier()

    # finally measure
    for i in range(qc.num_qubits - 1):
        qc.measure(i,i)

    return

def plot_histogram (counts, title=""):
    """
    Plots the histogram of the counts

    Args:

        counts (dict):
            The dictionary containing the counts of states

        titles (str):
            A title for the graph.

    Returns:
        None

    """
    fig, ax = plt.subplots(figsize =(10, 7))
    plt.xlabel("States")
    plt.ylabel("Counts")
    mylist = [key for key, val in counts.items() for _ in range(val)]

    unique, inverse = np.unique(mylist, return_inverse=True)
    bin_counts = np.bincount(inverse)

    plt.bar(unique, bin_counts)

    maxFreq = max(counts.values())
    plt.ylim(ymax=np.ceil(maxFreq / 10) * 10 if maxFreq % 10 else maxFreq + 10)
    # Show plot
    plt.title(title)
    plt.show()
    return

Step 3. Execute the algorithm and plot the results

# obtain the hidden string from the user
hiddenstring = int(input("Enter the hidden string as a binary value: "), 2)

#determine the number of qubits required to represent the hidden string
numberofqubits = hiddenstring.bit_length()

print(hiddenstring, numberofqubits)

# add a qubit for the ancilla register
numberofqubits += 1

q = QuantumRegister(numberofqubits , 'q')
c = ClassicalRegister(numberofqubits , 'c')
qc = QuantumCircuit(q, c)

bv_func(qc, hiddenstring)

job = backend.run(qc, shots=shots)
job_monitor(job)
result = job.result()
counts = result.get_counts()

plot_histogram(counts,"")

An example histogram is shown below for the hidden string \(10101010101\).

A plot of the GHZ state.

Bernstein_Vaziranai algorithm is good teaching example where we create a superposition of \(2^n\) states and use quantum interferance. For the vector \(|a\rangle\), the probability amplitudes interfered constructively. For all other values, the probability amplitudes interfered destructively, and their values are 0. The quantum version of this algorithm gets a linear speed up advantage.