Ch.2 Multiple-Qubit States

$$ \sim * \backsim $$

Table of Contents

  1. § 4. Multi-Qubit Quantum Gates
  2. § 5. Tensor Products
  3. § 6. Partial Measurements
  4. Exercises
  5. Solutions

$$ \backsim * \sim $$

§ 4. Multi-Qubit Quantum Gates


Now that we understand single-qubit gates, let’s explore multi-qubit operators, which act on more than one qubit / statevector.

A multi-qubit state $$|XY\rangle$$ is the tensor product of two single-qubit states:

$$ |XY\rangle = |X\rangle \otimes |Y\rangle = |X\rangle|Y\rangle$$

Let’s discuss some crucial Quantum Gates that operaton on multi-qubit states.

- SWAP Gate

The SWAP gate exchanges the states of two qubits:

$$ \text{SWAP} |a\rangle |b\rangle = |b\rangle |a\rangle $$

Its matrix representation is shown below

Figure 6: SWAP Gate

Controlled Operations

A controlled $U$ operator performs an action on a target qubit based on the state of a control qubit.

For qubits defined in the computational basis states $|0\rangle$ and $|1\rangle$, a controlled $U$ operation can be represented as:

$$ CU = |0\rangle \langle 0| \otimes I_R + |1\rangle\langle 1| \otimes U $$

To illustrate this, let’s discuss the Controlled NOT (CNOT) gate.

- CX / CNOT Gate

The CNOT gate flips the target qubit if the control qubit is in state $|1\rangle$, and leaves the target qubit unchanged if the control qubit is in state $|0\rangle$.

The action of a CNOT gate is depicted as:

It leaves the control qubit unchanged regardless. Its matrix representation is:

Figure 7: CNOT Gate

Controlled operations can be performed on both single and multi-qubit states. The control or target can be a system of qubits. For example, the CCNOT (controlled-controlled NOT) gate, also known as the Toffoli Gate, is a CNOT gate with 2 control qubits and 1 target qubit. The target qubit gets inverted if and only if both control qubits are in state $|1\rangle$.

Now, let’s discuss how to generate multi-qubit states. We have already learned how to generate single-qubit states using the Statevector() class in the previous chapter. To generate a multi-qubit state, we need to understand how to perform tensor products.


§ 5. Tensor Products


First, let’s create the multi-qubit statevector $|01\rangle$ as a tensor product of $|0\rangle \otimes |1\rangle$:

zero, one = Statevector.from_label("0"), Statevector.from_label("1")
zero.tensor(one).draw("latex")

Next, let’s create the statevectors $|+\rangle$ and $\frac{1}{\sqrt{2}}(|0\rangle + i|1\rangle)$, and combine them to form a new statevector $|\psi\rangle$:

plus = Statevector.from_label("+")
i_state = Statevector([1/sqrt(2), 1j/sqrt(2)])

psi = plus.tensor(i_state)
psi.draw("latex")

We can also take the tensor product of operators. The tensor product $C$ of the operators $X$ and $Y$, where $C = X \otimes Y$, can act simultaneously on a 2-qubit system. This is equivalent to applying $X$ to the first qubit and $Y$ to the second qubit, and then taking the tensor product.

To illustrate this, consider the following circuit:

circuit = QuantumCircuit(2)

circuit.x(0)
circuit.y(1) 
display(circuit.draw())

This is the same as applying the $C = X \otimes Y$ gate on the tensor product $q_0 \otimes q_1$.

We can generate tensor products between operators in the same way as with statevectors:

X = Operator([[0, 1], [1, 0]])
Y = Operator([[0, -1j], [1j, 0]])

C = X.tensor(Y)
print(C)

Let’s show that applying $X$ on qubit $|0\rangle$ and $Y$ on qubit $|1\rangle$ and then taking the resulting tensor product, is the same as applying $C = X \otimes Y$ to $|01\rangle = |0\rangle \otimes |1\rangle$.

ket0 = Statevector([1, 0])
ket1 = Statevector([0, 1])
ket_combined = ket0.tensor(ket1)

# individually applying X and Y to |0> and |1> separately
ket0f = ket0.evolve(X)
ket1f = ket1.evolve(Y)
ket_combinedf = ket0f.tensor(ket1f) 

print("X|0> ⊗ Y|1> ")
display(ket_combinedf.draw("latex"))

# applying combined operator to both, 
# redefining ket_combinedf using ket_combined 
ket_combinedf = ket_combined.evolve(X ^ Y) # X ^ Y = X ⊗ Y = C

print("(X ⊗ Y)|01>")
display(ket_combinedf.draw("latex"))

They’re the same! Let’s go back to the $|\psi\rangle$ vector we defined earlier,

psi.draw("latex")

Now, let’s create a CNOT operator and calculate $CNOT|\psi\rangle$.

CX = Operator(
    [
        [1, 0, 0, 0], 
        [0, 1, 0, 0], 
        [0, 0, 0, 1],
        [0, 0, 1, 0], 
    ]
)

psi.evolve(CX).draw("latex")

Compare the coefficients of the $|10\rangle$ and $|11\rangle$ states.

Now that we understand how to implement operators on multi-qubit states, let’s move on to implementing partial measurements on individual qubits of a multi-qubit state.


§ 6. Partial Measurements


In the previous chapter, we used the measure() method to simulate a measurement of a quantum statevector. This method returned the measured qubit value, and the resultant collapsed statevector post-measurement.

By default, `measure()`` measures all qubits in the statevector. However, we can provide a list of integers to measure only the qubits at those indices.

The following example demonstrates how to create and partially measure a specific state. We will create the state

$$ W = \frac{1}{\sqrt{3}} ( |001\rangle + |010\rangle + |100\rangle). $$

using the Statevector() class:

W = Statevector([0, 1, 1, 0, 1, 0, 0, 0] / sqrt(3))
W.draw("latex")

To understand how the above state was generated using Statevector(), note that each parameter index (0 or 1) represents a possible 3-qubit state:

The statevector $W$ has a value of 1 at indices 1, 2, and 4, indicating the presence of the states $|001\rangle$, $|010\rangle$, and $|100\rangle$.

Let’s simulate a measurement on the rightmost qubit (which has index 0). Note that this is the opposite of the usual convention, where qubit indices start from the right.

result, new_statevector = W.measure([0]) # measure qubit 0

print(f"Measured: {result}\nState after measurement:")
new_statevector.draw("latex")

Run the above cell multiple times to observe different results. Notice the following:

In the next chapter, we will explore `QuantumCircuit()``, which allows us to fully manipulate and work with any $n$-qubit state. Before moving on, try the following exercises to ensure you understand how to generate and measure multi-qubit states.


Exercises

  1. Generate the tensor product $|+\rangle |-\rangle$ and implement a CNOT gate on the result. The CNOT gate should have the first, left-most qubit as the control.

  2. Generate the tensor product operator $U$ between the Pauli-$X$ and Pauli-$Y$ operators ($U = X \otimes Y$). Show that applying $U |+\rangle |-\rangle$ yields the same result as $X|+\rangle \otimes Y|-\rangle$.

$\hspace{1px}$

$\hspace{1px}$

$\hspace{1px}$

$\hspace{1px}$

$\hspace{1px}$

$\hspace{1px}$

Solutions

Problem 1.

Let’s first generate the $|+\rangle |-\rangle$ statevector.

from qiskit.quantum_info import Statevector, Operator

plus = Statevector.from_label("+")
minus = Statevector.from_label("-")

plusminus = plus.tensor(minus) # Generating |+>|->
plusminus.draw('latex')

Let’s build the CNOT matrix and apply it on $|+\rangle |-\rangle$.

CNOT = Operator(
    [ 
        [1, 0, 0, 0], 
        [0, 1, 0, 0], 
        [0, 0, 0, 1], 
        [0, 0, 1, 0]
    ]
)

plusminus.evolve(CNOT).draw("latex")

We see that the coefficients in front of the $|10\rangle$ and $|11\rangle$ switched. (The $|11\rangle$ state became $|10\rangle$ and vice versa).

I present a second method using qiskit.QuantumCircuit(). We have not fully discussed QuantumCircuit yet, so consider this a sneak peek.

from qiskit import QuantumCircuit # We have not done fully introduced QuantumCircuits yet
                                  # You can consider this a sneak-peek

circuit = QuantumCircuit(2)

circuit.cx(1, 0) # CNOT gate with the left-most (index 1) qubit as the control

display(circuit.draw())

plusminus = Statevector.from_label("+").tensor(Statevector.from_label("-")) # Generating |+>|->
plusminus.evolve(circuit).draw('latex')

Problem 2.

Let’s first create the Pauli-$X$ and Pauli-$Y$ operators as well as the operator $U = X\otimes Y.$

from qiskit.quantum_info import Operator

X = Operator(
    [
        [0, 1], 
        [1, 0]
    ]
)

Y = Operator(
    [
        [0, -1j], 
        [1j, 0]
    ]
)

U = X.tensor(Y) 
U.draw("latex")

Now let’s generate the $|+\rangle$ and $|-\rangle$ states, along with their tensor product $|+\rangle |-\rangle$ and get the results of each operation from the question.

plus = Statevector.from_label('+')
minus = Statevector.from_label('-')

plusminus = plus.tensor(minus)


#Implementing U|+>|-> 
print("U|+>|->")
display(plusminus.evolve(U).draw('latex'))

# Implementing X|+> ⊗ Y|->
print("X|+> ⊗ Y|->")
display(plus.evolve(X).tensor(minus.evolve(Y)).draw('latex'))

Previous – Single-Qubit States $\sim$*$\backsim$ Next – Quantum Circuits