Phase Kickback

1. Exploring the CNOT-Gate

In the previous section, we saw some very basic results with the CNOT gate. Here we will explore some more interesting results.

We saw that we could entangle the two qubits by placing the control qubit in the state $|+\rangle$:

$$ \text{CNOT}|0{+}\rangle = \tfrac{1}{\sqrt{2}}(|00\rangle + |11\rangle) $$

But what happens if we put the second qubit in superposition?

from qiskit import QuantumCircuit, Aer, execute
from math import pi
import numpy as np
from qiskit.visualization import plot_bloch_multivector, plot_histogram
# In Jupyter Notebooks we can display this nicely using Latex.
# If not using Jupyter Notebooks you may need to remove the 
# array_to_latex function and use print() instead.
from qiskit_textbook.tools import array_to_latex
qc = QuantumCircuit(2)
qc.h(0)
qc.h(1)
qc.cx(0,1)
qc.draw()

In the circuit above, we have the CNOT acting on the state:

$$ |{+}{+}\rangle = \tfrac{1}{2}(|00\rangle + |01\rangle + |10\rangle + |11\rangle) $$

Since the CNOT swaps the amplitudes of $|01\rangle$ and $|11\rangle$, we see no change:

qc = QuantumCircuit(2)
qc.h(0)
qc.h(1)
qc.cx(0,1)
display(qc.draw())

# Let's see the result
statevector_backend = Aer.get_backend('statevector_simulator')
final_state = execute(qc,statevector_backend).result().get_statevector()
array_to_latex(final_state, pretext="\\text{Statevector} = ", precision=1)
plot_bloch_multivector(final_state)
$\displaystyle \text{Statevector} = \begin{bmatrix} \tfrac{1}{2} \\ \tfrac{1}{2} \\ \tfrac{1}{2} \\ \tfrac{1}{2} \end{bmatrix} $

Let’s put the target qubit in the state $|-\rangle$, so it has a negative phase:

qc = QuantumCircuit(2)
qc.h(0)
qc.x(1)
qc.h(1)
qc.draw()

This creates the state:

$$ |{-}{+}\rangle = \tfrac{1}{2}(|00\rangle + |01\rangle - |10\rangle - |11\rangle) $$
qc = QuantumCircuit(2)
qc.h(0)
qc.x(1)
qc.h(1)
display(qc.draw())
# See the result
final_state = execute(qc,statevector_backend).result().get_statevector()
array_to_latex(final_state, pretext="\\text{Statevector} = ", precision=1)
plot_bloch_multivector(final_state)
$\displaystyle \text{Statevector} = \begin{bmatrix} \tfrac{1}{2} \\ \tfrac{1}{2} \\ -\tfrac{1}{2} \\ -\tfrac{1}{2} \end{bmatrix} $

If the CNOT acts on this state, we will swap the amplitudes of $|01\rangle$ and $|11\rangle$, resulting in the state:

$$ \begin{aligned} \text{CNOT}|{-}{+}\rangle & = \tfrac{1}{2}(|00\rangle - |01\rangle - |10\rangle + |11\rangle) \\ \text{CNOT}|{-}{+}\rangle & = |{-}{-}\rangle \end{aligned} $$

This is interesting, because it affects the state of the control qubit while leaving the state of the target qubit unchanged.

qc.cx(0,1)
display(qc.draw())

final_state = execute(qc,statevector_backend).result().get_statevector()
array_to_latex(final_state, pretext="\\text{Statevector} = ", precision=1)
plot_bloch_multivector(final_state)
$\displaystyle \text{Statevector} = \begin{bmatrix} \tfrac{1}{2} \\ -\tfrac{1}{2} \\ -\tfrac{1}{2} \\ \tfrac{1}{2} \end{bmatrix} $

If you remember the H-gate transforms $|{+}\rangle \rightarrow |0\rangle$ and $|{-}\rangle \rightarrow |1\rangle$, we can see that wrapping a CNOT in H-gates has the equivalent behaviour of a CNOT acting in the opposite direction:

cnot_identity

We can verify this using Qiskit's unitary simulator:

qc = QuantumCircuit(2)
qc.h(0)
qc.h(1)
qc.cx(0,1)
qc.h(0)
qc.h(1)
display(qc.draw()) 
# `display` is an IPython tool, remove if it causes an error
unitary_backend = Aer.get_backend('unitary_simulator')
unitary = execute(qc,unitary_backend).result().get_unitary()
array_to_latex(unitary, pretext="\\text{Circuit = }\n")
$\displaystyle \text{Circuit = } \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 1 \\ 0 & 0 & 1 & 0 \\ \end{bmatrix} $$ $
qc = QuantumCircuit(2)
qc.cx(1,0)
display(qc.draw())

unitary_backend = Aer.get_backend('unitary_simulator')
unitary = execute(qc,unitary_backend).result().get_unitary()
array_to_latex(unitary, pretext="\\text{Circuit = }\n")
$\displaystyle \text{Circuit = } \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 1 \\ 0 & 0 & 1 & 0 \\ \end{bmatrix} $$ $

This identity is an example of phase kickback, which leads us neatly on to the next section...

2. Phase Kickback

2.1 Explaining the CNOT Circuit Identity

In the previous section we saw this identity:

cnot_identity

This is an example of kickback (or, phase kickback ) which is very important and is used in almost every quantum algorithm. Kickback is where the eigenvalue added by a gate to a qubit is ‘kicked back’ into a different qubit via a controlled operation. For example, we saw that performing an X-gate on a $|{-}\rangle$ qubit gives it the phase $-1$:

$$ X|{-}\rangle = -|{-}\rangle $$

When our control qubit is in either $|0\rangle$ or $|1\rangle$, this phase affects the whole state, however it is a global phase and has no observable effects:

$$ \begin{aligned} \text{CNOT}|{-}0\rangle & = |{-}\rangle \otimes |0\rangle \\ & = |{-}0\rangle \\ \quad & \\ \text{CNOT}|{-}1\rangle & = X|{-}\rangle \otimes |1\rangle \\ & = -|{-}\rangle \otimes |1\rangle \\ & = -|{-}1\rangle \\ \end{aligned} $$

The interesting effect is when our control qubit is in superposition, this phase factor applies to the target qubit only when it is in the state $|1\rangle$, which adds a relative phase to our control qubit:

$$ \begin{aligned} \text{CNOT}|{-}{+}\rangle & = \tfrac{1}{\sqrt{2}}(\text{CNOT}|{-}0\rangle + \text{CNOT}|{-}1\rangle) \\ \text{CNOT}|{-}{+}\rangle & = \tfrac{1}{\sqrt{2}}(|{-}0\rangle + X|{-}1\rangle) \\ \text{CNOT}|{-}{+}\rangle & = \tfrac{1}{\sqrt{2}}(|{-}0\rangle -|{-}1\rangle) \\ \end{aligned} $$

This can then be written as the two separable qubit states:

$$ \begin{aligned} \text{CNOT}|{-}{+}\rangle & = |{-}\rangle \otimes \tfrac{1}{\sqrt{2}}(|{0}\rangle - |1\rangle )\\ \text{CNOT}|{-}{+}\rangle & = |{-}{-}\rangle \\ \end{aligned} $$

Wrapping the CNOT in H-gates transforms the qubits from the computational basis to the $(|+\rangle, |-\rangle)$ basis, where we see this effect. This identity is very useful in hardware, since some hardwares only allow for CNOTs in one direction between two specific qubits. We can use this identity to overcome this problem and allow CNOTs in both directions.

2.2 Kickback with the T-gate

Let’s look at another controlled operation, the controlled-T gate:

qc = QuantumCircuit(2)
qc.cu1(pi/4, 0, 1)
qc.draw()

The T-gate has the matrix:

$$ \text{T} = \begin{bmatrix} 1 & 0 \\ 0 & e^{i\pi/4}\\ \end{bmatrix} $$

And the controlled-T gate has the matrix:

$$ \text{Controlled-T} = \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & e^{i\pi/4}\\ \end{bmatrix} $$

We can verify this using Qiskit's unitary simulator:

qc = QuantumCircuit(2)
qc.cu1(pi/4, 0, 1)
display(qc.draw())
# See Results:
unitary_backend = Aer.get_backend('unitary_simulator')
unitary = execute(qc,unitary_backend).result().get_unitary()
array_to_latex(unitary, pretext="\\text{Controlled-T} = \n")
$\displaystyle \text{Controlled-T} = \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & \tfrac{1}{\sqrt{2}}(1 + i) \\ \end{bmatrix} $$ $

More generally, we can find the matrix of any controlled-U operation using the rule:

$$ \begin{aligned} \text{U} & = \begin{bmatrix} u_{00} & u_{01} \\ u_{10} & u_{11}\\ \end{bmatrix} \\ \quad & \\ \text{Controlled-U} & = \begin{bmatrix} I & 0 \\ 0 & U\\ \end{bmatrix} = \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & u_{00} & u_{01} \\ 0 & 0 & u_{10} & u_{11}\\ \end{bmatrix} \end{aligned} $$

Or, using Qiskit's qubit ordering:

$$ \text{Controlled-U} = \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & u_{00} & 0 & u_{01} \\ 0 & 0 & 1 & 0 \\ 0 & u_{10} & 0 & u_{11}\\ \end{bmatrix} $$

If we apply the T-gate to a qubit in the state $|1\rangle$, we add a phase of $e^{i\pi/4}$ to this qubit:

$$ T|1\rangle = e^{i\pi/4}|1\rangle $$

This is global phase and is unobservable. But if we control this operation using another qubit in the $|{+}\rangle$ state, the phase is no longer global but relative, which changes the relative phase in our control qubit:

$$ \begin{aligned} |1{+}\rangle & = |1\rangle \otimes \tfrac{1}{\sqrt{2}}(|0\rangle + |1\rangle) \\ & = \tfrac{1}{\sqrt{2}}(|10\rangle + |11\rangle) \\ & \\ \text{Controlled-T}|1{+}\rangle & = \tfrac{1}{\sqrt{2}}(|10\rangle + e^{i\pi/4}|11\rangle) \\ & \\ \text{Controlled-T}|1{+}\rangle & = |1\rangle \otimes \tfrac{1}{\sqrt{2}}(|0\rangle + e^{i\pi/4}|1\rangle) \end{aligned} $$

This has the effect of rotating our control qubit around the Z-axis of the Bloch sphere, while leaving the target qubit unchanged. Let's see this in Qiskit:

qc = QuantumCircuit(2)
qc.h(0)
qc.x(1)
display(qc.draw())
# See Results:
final_state = execute(qc,statevector_backend).result().get_statevector()
plot_bloch_multivector(final_state)
qc = QuantumCircuit(2)
qc.h(0)
qc.x(1)
# Add Controlled-T
qc.cu1(pi/4, 0, 1)
display(qc.draw())
# See Results:
final_state = execute(qc,statevector_backend).result().get_statevector()
plot_bloch_multivector(final_state)