$$ CNOT =
\begin{bmatrix}
1 & 0 & 0 & 0 \\
0 & 1 & 0 & 0 \\
0 & 0 & 0 & 1 \\
0 & 0 & 1 & 0 \\
\end{bmatrix}
$$
But what does this matrix mean? The above matrix means: on a two qubit system (such as $\left|00\right>$, $\left|10\right>$, $\left|11\right>$, etc.) if the first qubit is a one, apply the not gate (X) on the second qubit. That's cool, but what if you have a 4 qubit system $\left(\left|0101\right>\right)$ or an 8 qubit system $\left(\left|00010011\right>\right)$ ? Also, let's say on the 8 qubit system you want to apply the X gate on the 7th qubit if the 2nd is one, how would you go about constructing this matrix?
Cookbook Method
Most books and articles will discuss a cookbook method where you build a matrix based on what the operation does to the base states. Let's work through this for the 2 qubit CNOT gate above. The above gate says if the first qubit is 1 then apply an X to the 2nd qubit. An X just changes the qubit from a 0 to a 1 or a 1 to a 0. The base states for a two qubit system are: $\left|00\right>,\, \left|01\right>,\, \left|10\right>,\, \left|11\right>$
How do I know those are the 4 states? Quantum qubit systems grow as $2^q$ where $q$ is the number of qubits. For a two qubit system $q=2$ therefore there are 4 qubit states. Then you count from 0 to 3 in binary. That is all.
First, this is how CNOT operates on the base states:
$$ CNOT\left|00\right> \rightarrow \left|00\right> $$$$ CNOT\left|01\right> \rightarrow \left|01\right> $$$$ CNOT\left|10\right> \rightarrow \left|11\right> $$$$ CNOT\left|11\right> \rightarrow \left|10\right> $$
To build a matrix you use the following trick:
\begin{align*}
CNOT &= \begin{bmatrix}\left<00|CNOT|00\right> && \left<00|CNOT|01\right> && \left<00|CNOT|10\right> && \left<00|CNOT|11\right> \\
\left<01|CNOT|00\right> && \left<01|CNOT|01\right> && \left<01|CNOT|10\right> && \left<01|CNOT|11\right> \\
\left<10|CNOT|00\right> && \left<10|CNOT|01\right> && \left<10|CNOT|10\right> && \left<10|CNOT|11\right> \\
\left<11|CNOT|00\right> && \left<11|CNOT|01\right> && \left<11|CNOT|10\right> && \left<11|CNOT|11\right> \end{bmatrix} \\ \\
&= \begin{bmatrix}\left<00|00\right> && \left<00|01\right> && \left<00|11\right> && \left<00|10\right> \\
\left<01|00\right> && \left<01|01\right> && \left<01|11\right> && \left<01|10\right> \\
\left<10|00\right> && \left<10|01\right> && \left<10|11\right> && \left<10|10\right> \\
\left<11|00\right> && \left<11|01\right> && \left<11|11\right> && \left<11|10\right> \end{bmatrix} \\ \\
&=\begin{bmatrix}
1 & 0 & 0 & 0 \\
0 & 1 & 0 & 0 \\
0 & 0 & 0 & 1 \\
0 & 0 & 1 & 0 \\
\end{bmatrix}
\end{align*}
This is nice, but now suppose you want a CNOT gate between the 3rd and 9th qubit of a 10 qubit system? You will have $2^{10}$ base states and a $2^{10} \times 2^{10}$ matrix! Good luck with the cookbook method! What we need is an algorithmic method that we can code up in Python.
The Algorithmic Method
A better algorithmic way to think about quantum control gates is by using operators and tensor products. Suppose we have a two qubit system.
To say "If the first qubit is $\left|0\right>$ leave the second qubit alone":
$$ \left|0\rangle\langle0\right| \otimes I $$
to leave a qubit alone you apply the Identity operator / matrix $I$
To say "If the first qubit is $\left|1\right>$ apply X to the second qubit":
$$ \left|1\rangle\langle1\right| \otimes X $$
Now put them together by adding them, "If the first qubit is $\left|0\right>$ leave the second qubit alone and If the first qubit is $\left|1\right>$ apply X to the second qubit":
$$ \left|0\rangle\langle0\right| \otimes I + \left|1\rangle\langle1\right| \otimes X $$
In [3]:
from qudotpy import qudot
import numpy as np
zero_matrix = qudot.ZERO.ket * qudot.ZERO.bra
one_matrix = qudot.ONE.ket * qudot.ONE.bra
CNOT = np.kron(zero_matrix, np.eye(2)) + np.kron(one_matrix, qudot.X.matrix)
print(CNOT)
[[ 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.+0.j 1.+0.j]
[ 0.+0.j 0.+0.j 1.+0.j 0.+0.j]]
Nice! This is a good algorithmic way to make CNOT gates (or control gates in general). For example, suppose now you want a CNOT gate of a 10 qubit system where the 3rd qubit is the control and the 9th qubit is the target:
"If the third qubit is $\left|0\right>$ leave the ninth qubit alone:"
$$ I \otimes I \otimes \left|0\rangle\langle0\right| \otimes I \otimes I \otimes I \otimes I \otimes I \otimes I \otimes I $$
"If the third qubit is $\left|1\right>$ apply X to the ninth qubit":
$$ I \otimes I \otimes \left|1\rangle\langle1\right| \otimes I \otimes I \otimes I \otimes I \otimes I \otimes X \otimes I $$
Add those two expression together and you have your 10 qubit CNOT gate.
The above algorithm is straightforward to implement in Python.