Qubits Representation In Ekert Simulation

Learning Lab
My Journey Through Books, Discoveries, and Ideas

Qubits representation in Ekert simulation

I am developing a Python simulation for the Ekert 1991 quantum key distribution protocol. Unlike BB84, Ekert relies on the properties of quantum entanglement. This post covers the first part: representing the entangled state and simulating local measurements performed by Alice and Bob.

Representing entangled qubit pairs

The Ekert protocol typically uses a source emitting pairs of qubits in a maximally entangled state, such as the Bell state |\Phi^+\rangle. I represent this state in the simulation using the E91QubitPair class. The state vector is constructed using the tensor product (\otimes) of the computational basis states |\mathbf 0\rangle and |\mathbf 1\rangle:

|\Phi^+\rangle = \frac{1}{\sqrt{2}} (|\mathbf 0\rangle_A \otimes |\mathbf 0\rangle_B + |\mathbf 1\rangle_A \otimes |\mathbf 1\rangle_B) = \frac{1}{\sqrt{2}} (|\mathbf {00}\rangle + |\mathbf {11}\rangle)

In the code, this corresponds to a 4x1 complex vector:


import numpy as np
# Assuming ket0(), ket1(), tensor(), normalize() are defined as before

class E91QubitPair:
    # Represents a maximally entangled Bell state: (|00⟩ + |11⟩)/√2
    def __init__(self):
        psi = (1 / np.sqrt{2}) * (tensor(ket0(), ket0())
                                  + tensor(ket1(), ket1()))
        # Store as 4x1 column vector
        self.mState = psi.reshape((4, 1))

Simulating local measurements

In the Ekert protocol, Alice and Bob receive one qubit each from the entangled pair. They independently choose measurement settings (bases) and measure their respective qubits. My simulation models this using a localMeasure function. Similar to the generalized BB84 implementation, measurement bases are defined by an angle \theta, corresponding to the observable \mathbf{O}(\theta) = \cos(\theta) \boldsymbol{\sigma}_x + \sin(\theta) \boldsymbol{\sigma}_z.

Performing a measurement on only one part (e.g., Alice’s qubit A) of the entangled state |\boldsymbol \Psi\rangle_{AB} requires calculating the probability of obtaining outcome i (corresponding to eigenstate |\mathbf v_i\rangle_A of \mathbf{O}(\theta_A)). This involves projecting the joint state onto Alice’s measurement basis while tracing over Bob’s part, or using projection operators acting only on the relevant subspace.

The probability of Alice obtaining outcome i when measuring in basis \{|\mathbf v_0\rangle_A, |\mathbf v_1\rangle_A\} is:

\mathcal P(i | A) = \langle \boldsymbol \Psi | (\mathbf{P}_{i,A} \otimes \mathbb{I}_B) | \Psi \rangle

where \mathbf{P}_{i,A} = |\mathbf v_i\rangle_A \langle \mathbf v_i|_A is the projection operator for outcome i on Alice’s side, and \mathbb{I}_B is the identity operator on Bob’s side.

After the measurement yields outcome i, the state of the two-qubit system collapses to:

|\boldsymbol \Psi^\prime\rangle_{AB} = \frac{(\mathbf{P}_{i,A} \otimes \mathbb{I}_B) |\boldsymbol \Psi\rangle_{AB}}{\sqrt{\mathcal P(i | A)}}

The localMeasure function implements this process. It takes the joint state, the measurement angle \theta, and which qubit (A or B) is being measured.


def localMeasure(state, theta_deg, which: str):
    # Perform a local measurement on side 'A' or 'B'
    obs = observable(theta_deg) # Observable for the chosen angle
    _, eigvecs = np.linalg.eigh(obs)
    basis = [eigvecs[:, 0:1], eigvecs[:, 1:2]] # Measurement eigenstates |v₀⟩, |v₁⟩

    psi = state.reshape(4, 1) # The joint state |Ψ⟩_AB

    # Calculate outcome probabilities using appropriate projections
    # (simplified view of the matrix math)
    # For measurement on A: Probs based on projecting |Ψ⟩ onto basis[i]_A
    # For measurement on B: Probs based on projecting |Ψ⟩ onto basis[i]_B
    # ... calculation of probs ...

    # Choose outcome based on probabilities
    result = np.random.choice([0, 1], p=probs)
    vec = basis[result] # The eigenstate |v_result⟩ corresponding to the outcome

    # Construct the projection operator for the measured side
    proj_op = vec @ vec.conj().T # Operator |v_result⟩⟨v_result|
    if which == 'A':
        # Projector acting on Alice qubit: P_A ⊗ I_B
        proj = tensor(proj_op, np.eye(2))
    else:
        # Projector acting on Bob's qubit: I_A ⊗ P_B
        proj = tensor(np.eye(2), proj_op)

    # Apply the projection to the joint state
    collapsed = proj @ psi
    collapsed = normalize(collapsed) # Normalize the collapsed state |Ψ'⟩_AB

    # Extract the state of the *other* particle (reduced density matrix concept)
    # This gives the state Bob holds after Alice measures, or vice versa.
    psi2 = collapsed.reshape(2, 2)
    if which == 'A': # Alice measured, find Bob resulting state
        reduced = psi2[result, :].reshape(2, 1) # Indexing depends on measurement result
    else: # Bob measured, find Alice's resulting state
        reduced = psi2[:, result].reshape(2, 1)
    reduced = normalize(reduced)

    # Return classical outcome and the state of the other qubit
    return result, reduced

The function computes the probabilities, simulates the random outcome, applies the correct projection operator (\mathbf{P}_{i,A} \otimes \mathbb{I}_B or \mathbb{I}_A \otimes \mathbf{P}_{i,B}) to collapse the state, and normalizes the result. Crucially for the simulation sequence, it returns not only the classical measurement outcome (0 or 1) but also the resulting quantum state of the other particle after the collapse.

These components—the representation of the entangled Bell state and the localMeasure function—form the quantum mechanical core needed to simulate the Ekert protocol. The next step involves using these in the context of the full protocol, including measurement sequences by Alice and Bob, potential eavesdropping, and the reconciliation process involving the CHSH inequality.

I previously discussed the theoretical framework in a previous posts here.

For access to the complete simulation code, please visit the GitHub repository here.