# Examples¶

This page demonstrates how QCircuits can be used to simulate example quantum algorithms.

# Producing Bell States¶

Example code producing each of the four entangled Bell states for a two-qubit system.

The circuit diagram is where |x⟩ and |y⟩ are each one of the computational basis states, |0⟩ or |1⟩.

E.g., $$|\beta_{00}⟩ = \frac{1}{\sqrt{2}} (|00⟩ + |11⟩)$$.

Code:

import qcircuits as qc
from itertools import product

# Creates each of the four Bell states

def bell_state(x, y):
CNOT = qc.CNOT()

phi = qc.bitstring(x, y)
phi = H(phi, qubit_indices=)

return CNOT(phi)

if __name__ == '__main__':

for x, y in product([0, 1], repeat=2):

print('\nInput: {} {}'.format(x, y))
print('Bell state:')
print(bell_state(x, y))


# Quantum Teleportation¶ Code:

import qcircuits as qc

# Quantum Teleportation: transmitting two classical bits to transport a qubit state
# Alice has a qubit in a given quantum state.
# Alice and Bob have previously prepared a Bell state, and have since
# physically separated the qubits.
# Alice manipulates her hidden qubit and her half of the Bell state, and then
# measures both qubits.
# She sends the result (two classical bits) to Bob, who is able to reconstruct
# Alice's state by applying operators based on the measurement outcomes.

def quantum_teleportation(alice_state):
# Get operators we will need
CNOT = qc.CNOT()
X = qc.PauliX()
Z = qc.PauliZ()

# The prepared, shared Bell state
bell = qc.bell_state(0, 0)
# The whole state vector
state = alice_state * bell

# Apply CNOT and Hadamard gate
state = CNOT(state, qubit_indices=[0, 1])
state = H(state, qubit_indices=)

# Measure the first two bits
# The only uncollapsed part of the state vector is Bob's
M1, M2 = state.measure(qubit_indices=[0, 1], remove=True)

# Apply X and/or Z gates to third qubit depending on measurements
if M2:
state = X(state)
if M1:
state = Z(state)

return state

if __name__ == '__main__':
# Alice's original state to be teleported to Bob
alice = qc.qubit(theta=1.5, phi=0.5, global_phase=0.2)

# Bob's state after quantum teleportation
bob = quantum_teleportation(alice)

print('Original state:', alice)
print('\nTeleported state:', bob)


# Quantum Parallelism¶ Code:

import qcircuits as qc
import numpy as np

# Example of quantum parallelism

# Construct a Boolean function
def construct_problem():

def f(bit):

return f

def quantum_parallelism(f):
U_f = qc.U_f(f, d=2)

phi = qc.zeros(2)
phi = H(phi, qubit_indices=)
phi = U_f(phi)

if __name__ == '__main__':
f = construct_problem()

quantum_parallelism(f)


# Deutsch’s Algorithm¶ Code:

import qcircuits as qc
import numpy as np

# Deutsch's Algorithhm:
# We use interference to determine if f(0) = f(1) using a single function evaluation.

# Construct a Boolean function that is constant or balanced
def construct_problem():

def f(bit):

return f

def deutsch_algorithm(f):
U_f = qc.U_f(f, d=2)

phi = H(qc.zeros()) * H(qc.ones())
phi = U_f(phi)
phi = H(phi, qubit_indices=)

measurement = phi.measure(qubit_indices=0)
return measurement

if __name__ == '__main__':
f = construct_problem()
parity = f(0) == f(1)

measurement = deutsch_algorithm(f)

print('f(0): {}, f(1): {}'.format(f(0), f(1)))
print('f(0) == f(1): {}'.format(parity))
print('Measurement: {}'.format(measurement))


# The Deutsch-Jozsa Algorithm¶ Code:

import qcircuits as qc
import numpy as np
import random

# Deutsch-Jozsa Algorithhm:
# We are presented with a Boolean function that is either constant or
# balanced (i.e., 0 for half of inputs, 1 for the other half).
# We make use of interference to determine whether the function is constant
# or balanced in a single function evaluation.

# Construct a Boolean function that is constant or balanced
def construct_problem(d=1, problem_type='constant'):
num_inputs = 2**d

if problem_type == 'constant':
else: # function is balanced
indices = np.random.choice(num_inputs, size=num_inputs//2, replace=False)

def f(*bits):
index = sum(v * 2**i for i, v in enumerate(bits))

return f

def deutsch_jozsa_algorithm(d, f):
# The operators we will need
U_f = qc.U_f(f, d=d+1)

state = qc.zeros(d) * qc.ones(1)
state = (H_d * H)(state)
state = U_f(state)
state = H_d(state, qubit_indices=range(d))

measurements = state.measure(qubit_indices=range(d))
return measurements

if __name__ == '__main__':
d = 10
problem_type = random.choice(['constant', 'balanced'])

f = construct_problem(d, problem_type)
measurements = deutsch_jozsa_algorithm(d, f)

print('Problem type: {}'.format(problem_type))
print('Measurement: {}'.format(measurements))
print('Observed all zeros: {}'.format(not any(measurements)))


# Superdense Coding¶ Code:

import qcircuits as qc
import numpy as np

# Superdense Coding: transmitting a qubit to transport two classical bits
# Alice and Bob have previously prepared a Bell state, and have since
# physically separated the qubits.
# Alice has two classical bits she wants to transmit to Bob.
# She manipulates her half of the Bell state depending on the values of those bits,
# then transmits her qubit to Bob, who then measures the system.

def superdense_coding(bit_1, bit_2):
# Get operators we will need
CNOT = qc.CNOT()
X = qc.PauliX()
Z = qc.PauliZ()

# The prepared, shared Bell state
# Initially, half is in Alice's possession, and half in Bob's
phi = qc.bell_state(0, 0)

# Alice manipulates her qubit
if bit_2:
phi = X(phi, qubit_indices=)
if bit_1:
phi = Z(phi, qubit_indices=)

# Bob decodes the two bits
phi = CNOT(phi)
phi = H(phi, qubit_indices=)
measurements = phi.measure()
return measurements

if __name__ == '__main__':
# Alice's classical bits she wants to transmit
bit_1, bit_2 = np.random.randint(0, 2, size=2)

# Bob's measurements
measurements = superdense_coding(bit_1, bit_2)

print("Alice's initial bits:\t{}, {}".format(bit_1, bit_2))
print("Bob's measurements:\t{}, {}".format(measurements, measurements))


# Phase Estimation¶

We are give a black-box d-qubit operator U and one of its eigenstates. The task is to estimate the phase of the corresponding eigenvalue, storing the result in a t-qubit register.

Code:

import qcircuits as qc
import numpy as np
from scipy.stats import unitary_group

# Phase estimation
# We are given a black-box unitary operator, and one of its eigenstates.
# The task is to estimate the phase of the corresponding eigenvalue.
# This is done making use of the efficient inverse quantum Fourier transform.

# Prepares a state that when the inverse Fourier transform is applied,
# unpacks the binary fractional expansion of the phase into the
# first t-qubit register.
def stage_1(state, U, t, d):

# For each qubit in reverse order, apply the Hadamard gate,
# then apply U^(2^i) to the d-qubit register
# conditional on the state of the t-i qubit in the
# t-qubit register.
for idx, t_i in enumerate(range(t-1, -1, -1)):
U_2_idx = U
for app_i in range(idx):
U_2_idx = U_2_idx(U_2_idx)
C_U = qc.ControlledU(U_2_idx)
state = C_U(
state,
qubit_indices=[t_i] + list(range(t, t+d, 1))
)

return state

# The t-qubit quantum Fourier transform
def QFT(t):
Op = qc.Identity(t)

# The R_k gate applies a 2pi/2^k phase is the qubit is set
C_Rs = {}
for k in range(2, t+1, 1):
R_k = np.exp(np.pi * 1j / 2**k) * qc.RotationZ(2*np.pi / 2**k)
C_Rs[k] = qc.ControlledU(R_k)

# For each qubit in order, apply the Hadamard gate, and then
# apply the R_2, R_3, ... conditional on the remainder of the qubits
for t_i in range(t):
Op = H(Op, qubit_indices=[t_i])
for k in range(2, t+1 - t_i, 1):
Op = C_Rs[k](Op, qubit_indices=[t_i + k - 1, t_i])

# We have the QFT, but with the qubits in reverse order
# Swap them back
Swap = qc.Swap()
for i, j in zip(range(t), range(t-1, -1, -1)):
if i >= j:
break
Op = Swap(Op, qubit_indices=[i, j])

return Op

# The t-qubit inverse quantum Fourier transform
def inv_QFT(t):

# Do phase estimation for a random d-qubit operator,
# recording the result in a t-qubit register.
def phase_estimation(d=2, t=8):
# a d-qubit gate
U = unitary_group.rvs(2**d)
eigvals, eigvecs = np.linalg.eig(U)
U = qc.Operator.from_matrix(U)

# an eigenvector u and the phase of its eigenvalue, phi
phi = np.real(np.log(eigvals) / (2j*np.pi))
if phi < 0:
phi += 1
u = eigvecs[:, 0]
u = qc.State.from_column_vector(u)

state = qc.zeros(t) * u

state = stage_1(state, U, t, d)
state = inv_QFT(t)(state, qubit_indices=range(t))
measurement = state.measure(qubit_indices=range(t))
phi_estimate = sum(measurement[i] * 2**(-i-1) for i in range(t))

return phi, phi_estimate

if __name__ == '__main__':
phi, phi_estimate = phase_estimation(d=2, t=8)

print('True phase: {}'.format(phi))
print('Estimated phase: {}'.format(phi_estimate))


# Grover’s Algorithm¶

We are given a black-box boolean function f(x) that evaluates to 1 for exactly one value of x. Grover’s algorithm finds the solution with resources proportional to the square root of the size of the search space.

Code:

import qcircuits as qc
import numpy as np
import random

# Grover's algorithm (search)
# Given a boolean function f, Grover's algorithm finds an x such that
# f(x) = 1.
# If there are N values of x, and M possible solutions, it requires
# O(sqrt(N/M)) time.
# Here, we construct a search problem with 1 solution amongst 1024
# possible answers, and find the solution with 25 applications of
# the Grover iteration operator.

# Construct a Boolean function that is 1 in exactly one place
def construct_problem(d=10):
num_inputs = 2**d

def f(*bits):
index = sum(v * 2**i for i, v in enumerate(bits))

return f

def grover_algorithm(d, f):
# The operators we will need
Oracle = qc.U_f(f, d=d+1)
N = 2**d
zero_projector = np.zeros((N, N))
zero_projector[0, 0] = 1
Inversion = H_d((2 * qc.Operator.from_matrix(zero_projector) - qc.Identity(d))(H_d))
Grover = Inversion(Oracle, qubit_indices=range(d))

# Initial state
state = qc.zeros(d) * qc.ones(1)
state = (H_d * H)(state)

# Number of Grover iterations
angle_to_rotate = np.arccos(np.sqrt(1 / N))
rotation_angle = 2 * np.arcsin(np.sqrt(1 / N))
iterations = int(round(angle_to_rotate / rotation_angle))
for i in range(iterations):
state = Grover(state)

measurements = state.measure(qubit_indices=range(d))
return measurements

if __name__ == '__main__':
d = 10

f = construct_problem(d)
bits = grover_algorithm(d, f)

print('Measurement: {}'.format(bits))
print('Evaluate f at measurement: {}'.format(f(*bits)))