from qiskit import *
from math import pi
import numpy as np
from qiskit.visualization import plot_bloch_multivector,plot_state_qsphere
import matplotlib.pyplot as plt
The space of quantum computer grows exponential with the number of qubits. For n qubits the complex vector space has dimensions d=2n. To describe states of a multi-qubit system, the tensor product is used to "glue together" operators and basis vectors.
Let's start by considering a 2-qubit system. Given two operators A and B that each act on one qubit, the joint operator A⊗B acting on two qubits is
A⊗B=(A00(B00B01B10B11)A01(B00B01B10B11)A10(B00B01B10B11)A11(B00B01B10B11)),where Ajk and Blm are the matrix elements of A and B, respectively.
Analogously, the basis vectors for the 2-qubit system are formed using the tensor product of basis vectors for a single qubit: |00⟩=(1(10)0(10))=(1000) |01⟩=(1(01)0(01))=(0100)
Note we've introduced a shorthand for the tensor product of basis vectors, wherein |0⟩⊗|0⟩ is written as |00⟩. The state of an n-qubit system can described using the n-fold tensor product of single-qubit basis vectors. Notice that the basis vectors for a 2-qubit system are 4-dimensional; in general, the basis vectors of an n-qubit sytsem are 2n-dimensional, as noted earlier.
Within the physics community, the qubits of a multi-qubit systems are typically ordered with the first qubit on the left-most side of the tensor product and the last qubit on the right-most side. For instance, if the first qubit is in state |0⟩ and second is in state |1⟩, their joint state would be |01⟩. Qiskit uses a slightly different ordering of the qubits, in which the qubits are represented from the most significant bit (MSB) on the left to the least significant bit (LSB) on the right (big-endian). This is similar to bitstring representation on classical computers, and enables easy conversion from bitstrings to integers after measurements are performed. For the example just given, the joint state would be represented as |10⟩. Importantly, this change in the representation of multi-qubit states affects the way multi-qubit gates are represented in Qiskit, as discussed below.
The representation used in Qiskit enumerates the basis vectors in increasing order of the integers they represent. For instance, the basis vectors for a 2-qubit system would be ordered as |00⟩, |01⟩, |10⟩, and |11⟩. Thinking of the basis vectors as bit strings, they encode the integers 0,1,2 and 3, respectively.
A common multi-qubit gate involves the application of a gate to one qubit, conditioned on the state of another qubit. For instance, we might want to flip the state of the second qubit when the first qubit is in |0⟩. Such gates are known as controlled gates. The standard multi-qubit gates consist of two-qubit gates and three-qubit gates. The two-qubit gates are:
The three-qubit gates are:
Most of the two-gates are of the controlled type (the SWAP gate being the exception). In general, a controlled two-qubit gate CU acts to apply the single-qubit unitary U to the second qubit when the state of the first qubit is in |1⟩. Suppose U has a matrix representation
U=(u00u01u10u11).We can work out the action of CU as follows. Recall that the basis vectors for a two-qubit system are ordered as |00⟩,|01⟩,|10⟩,|11⟩. Suppose the control qubit is qubit 0 (which, according to Qiskit's convention, is one the right-hand side of the tensor product). If the control qubit is in |1⟩, U should be applied to the target (qubit 1, on the left-hand side of the tensor product). Therefore, under the action of CU, the basis vectors are transformed according to
CU:|0⟩qubit 1⊗|0⟩qubit 0→|0⟩qubit 1⊗|0⟩qubit 0CU:|0⟩qubit 1⊗|1⟩qubit 0→U|0⟩qubit 1⊗|1⟩qubit 0CU:|1⟩qubit 1⊗|0⟩qubit 0→|1⟩qubit 1⊗|0⟩qubit 0CU:|1⟩qubit 1⊗|1⟩qubit 0→U|1⟩qubit 1⊗|1⟩qubit 0.In matrix form, the action of CU is
CU=(10000u000u0100100u100u11).To work out these matrix elements, let
C(jk),(lm)=(⟨j|qubit 1⊗⟨k|qubit 0)CU(|l⟩qubit 1⊗|k⟩qubit 0),compute the action of CU (given above), and compute the inner products.
As shown in the examples below, this operation is implemented in Qiskit as cU(q[0],q[1])
.
If qubit 1 is the control and qubit 0 is the target, then the basis vectors are transformed according to CU:|0⟩qubit 1⊗|0⟩qubit 0→|0⟩qubit 1⊗|0⟩qubit 0CU:|0⟩qubit 1⊗|1⟩qubit 0→|0⟩qubit 1⊗|1⟩qubit 0CU:|1⟩qubit 1⊗|0⟩qubit 0→|1⟩qubit 1⊗U|0⟩qubit 0CU:|1⟩qubit 1⊗|1⟩qubit 0→|1⟩qubit 1⊗U|1⟩qubit 0,
which implies the matrix form of CU is CU=(1000010000u00u0100u10u11).
q = QuantumRegister(2)
The controlled-not gate flips the target
qubit when the control qubit is in the state |1⟩. If we take the MSB as the control qubit (e.g. cx(q[1],q[0])
), then the matrix would look like
However, when the LSB is the control qubit, (e.g. cx(q[0],q[1])
), this gate is equivalent to the following matrix:
qc = QuantumCircuit(2)
qc.cx(0,1)
qc.draw(output='mpl')
backend = Aer.get_backend('unitary_simulator')
job = execute(qc, backend)
result = job.result()
print(result.get_unitary(qc, decimals=3))
[[1.+0.j 0.+0.j 0.+0.j 0.+0.j] [0.+0.j 0.+0.j 0.+0.j 0.-1.j] [0.+0.j 0.+0.j 1.+0.j 0.+0.j] [0.+0.j 0.+1.j 0.+0.j 0.+0.j]]
Apply the Y gate to the target qubit if the control qubit is the MSB
CY=(10000100000−i00i0),or when the LSB is the control
CY=(1000000−i00100i00).qc = QuantumCircuit(2)
qc.cy(0,1)
qc.draw(output='mpl')
backend = Aer.get_backend('unitary_simulator')
job = execute(qc, backend)
result = job.result()
print(result.get_unitary(qc, decimals=3))
[[1.+0.j 0.+0.j 0.+0.j 0.+0.j] [0.+0.j 0.+0.j 0.+0.j 0.-1.j] [0.+0.j 0.+0.j 1.+0.j 0.+0.j] [0.+0.j 0.+1.j 0.+0.j 0.+0.j]]
Similarly, the controlled Z gate flips the phase of the target qubit if the control qubit is |1⟩. The matrix looks the same regardless of whether the MSB or LSB is the control qubit:
CZ=(100001000010000−1)qc = QuantumCircuit(2)
qc.cz(0,1)
qc.draw(output='mpl')
backend = Aer.get_backend('unitary_simulator')
job = execute(qc, backend)
result = job.result()
print(result.get_unitary(qc, decimals=3))
[[ 1.+0.j 0.+0.j 0.+0.j 0.+0.j] [ 0.+0.j 1.+0.j 0.+0.j 0.+0.j] [ 0.+0.j 0.+0.j 1.+0.j 0.+0.j] [ 0.+0.j 0.+0.j 0.+0.j -1.+0.j]]
Apply H gate to the target qubit if the control qubit is |1⟩. Below is the case where the control is the LSB qubit.
CH=(100001√201√2001001√20−1√2)qc = QuantumCircuit(2)
qc.ch(0,1)
qc.draw(output='mpl')
backend = Aer.get_backend('unitary_simulator')
job = execute(qc, backend)
result = job.result()
print(result.get_unitary(qc, decimals=3))
[[ 1. -0.j 0. +0.j -0. +0.j 0. +0.j] [ 0. +0.j 0.707-0.j 0. +0.j 0.707-0.j] [ 0. -0.j 0. +0.j 1. -0.j 0. +0.j] [ 0. +0.j 0.707-0.j 0. +0.j -0.707+0.j]]
qc = QuantumCircuit(2)
qc.crz(pi/2,0,1)
qc.draw(output='mpl')
backend = Aer.get_backend('unitary_simulator')
job = execute(qc, backend)
result = job.result()
print(result.get_unitary(qc, decimals=3))
[[1. +0.j 0. +0.j 0. +0.j 0. +0.j ] [0. +0.j 0.707-0.707j 0. +0.j 0. +0.j ] [0. +0.j 0. +0.j 1. +0.j 0. +0.j ] [0. +0.j 0. +0.j 0. +0.j 0.707+0.707j]]
Perform a phase rotation if both qubits are in the |11⟩ state. The matrix looks the same regardless of whether the MSB or LSB is the control qubit.
Cu1(λ)=(100001000010000eiλ)qc = QuantumCircuit(2)
qc.cu1(pi/2,0,1)
qc.draw(output='mpl')
backend = Aer.get_backend('unitary_simulator')
job = execute(qc, backend)
result = job.result()
print(result.get_unitary(qc, decimals=3))
[[1.+0.j 0.+0.j 0.+0.j 0.+0.j] [0.+0.j 1.+0.j 0.+0.j 0.+0.j] [0.+0.j 0.+0.j 1.+0.j 0.+0.j] [0.+0.j 0.+0.j 0.+0.j 0.+1.j]]
Perform controlled-u3 rotation on the target qubit if the control qubit (here LSB) is |1⟩.
Cu3(θ,ϕ,λ)≡(10000e−i(ϕ+λ)/2cos(θ/2)0−e−i(ϕ−λ)/2sin(θ/2)00100ei(ϕ−λ)/2sin(θ/2)0ei(ϕ+λ)/2cos(θ/2)).qc = QuantumCircuit(2)
qc.cu3(pi/2, pi/2, pi/2, 0,1)
qc.draw(output='mpl')
backend = Aer.get_backend('unitary_simulator')
job = execute(qc, backend)
result = job.result()
print(result.get_unitary(qc, decimals=3))
[[ 1. +0.j 0. +0.j 0. +0.j 0. +0.j ] [ 0. +0.j 0.707+0.j 0. +0.j -0. -0.707j] [ 0. +0.j 0. +0.j 1. +0.j 0. +0.j ] [ 0. +0.j 0. +0.707j 0. +0.j -0.707+0.j ]]
The SWAP gate exchanges the two qubits. It transforms the basis vectors as
|00⟩→|00⟩ , |01⟩→|10⟩ , |10⟩→|01⟩ , |11⟩→|11⟩,which gives a matrix representation of the form
SWAP=(1000001001000001).qc = QuantumCircuit(2)
qc.swap(0,1)
qc.draw(output='mpl')
backend = Aer.get_backend('unitary_simulator')
job = execute(qc, backend)
result = job.result()
print(result.get_unitary(qc, decimals=3))
[[1.+0.j 0.+0.j 0.+0.j 0.+0.j] [0.+0.j 0.+0.j 1.+0.j 0.+0.j] [0.+0.j 1.+0.j 0.+0.j 0.+0.j] [0.+0.j 0.+0.j 0.+0.j 1.+0.j]]