Bell State Preparation
Create and verify Bell (EPR) entangled states using pyqpanda3.
Problem
What is a Bell State?
A Bell state (also called an EPR pair, after Einstein, Podolsky, and Rosen) is a pair of qubits that are maximally entangled. The four Bell states form a complete orthonormal basis for the two-qubit Hilbert space:
The most commonly used one is 0, or both produce 1. Each outcome occurs with probability 01 and 10 never appear. This perfect correlation persists regardless of the physical distance between the qubits.
Why Bell States Matter
Bell states are foundational to quantum computing and quantum information for several reasons:
Entanglement verification. Bell states are the simplest examples of quantum entanglement. They are used in Bell inequality experiments (CHSH game) to demonstrate that nature cannot be described by local hidden-variable theories.
Quantum teleportation. A shared Bell pair is the quantum channel that enables teleportation of an arbitrary qubit state using only classical communication.
Superdense coding. By manipulating one qubit of a Bell pair, a sender can transmit two classical bits to a receiver who holds both qubits.
Quantum key distribution (QKD). Protocols such as E91 use Bell pairs to generate a shared secret key with information-theoretic security.
Error correction. Bell states are consumed in fault-tolerant protocols such as teleportation-based gate teleportation and lattice surgery for surface codes.
Benchmarking. Preparing and measuring Bell states is a standard calibration routine on real quantum hardware to assess two-qubit gate fidelity.
Solution
Circuit for the Basic Bell State
The standard recipe for preparing
- Hadamard on qubit 0. This creates a uniform superposition:
- CNOT with qubit 0 as control and qubit 1 as target. This entangles the two qubits:
- Measure both qubits to collapse the state and collect statistics.
Preparing All Four Bell States
Each Bell state can be reached from
| Target State | Extra Gate on Qubit 1 | Circuit |
|---|---|---|
| (none) | H(0) -- CNOT(0,1) | |
| Z(1) before CNOT | Z(1) -- H(0) -- CNOT(0,1) | |
| X(1) before CNOT | X(1) -- H(0) -- CNOT(0,1) | |
| Y(1) before CNOT | Y(1) -- H(0) -- CNOT(0,1) |
Equivalently, you can apply the correction after the CNOT, which is often more convenient when composing circuits:
| Target State | Correction After CNOT |
|---|---|
| (none) | |
| Z(0) | |
| X(0) | |
| Y(0) |
Code
Basic Bell State Preparation and Measurement
This is the minimal example: prepare
from pyqpanda3 import core
# Build the Bell state circuit.
# QProg is the top-level container for quantum operations.
prog = core.QProg()
# Step 1: Apply Hadamard to qubit 0.
# This puts qubit 0 into the equal superposition (|0> + |1>) / sqrt(2).
prog << core.H(0)
# Step 2: Apply CNOT with qubit 0 as control, qubit 1 as target.
# This correlates the two qubits, producing the Bell state |Phi+>.
prog << core.CNOT(0, 1)
# Step 3: Measure both qubits into classical bits 0 and 1.
prog << core.measure([0, 1], [0, 1])
# Create the CPU statevector simulator and run 10,000 shots.
machine = core.CPUQVM()
machine.run(prog, shots=10000)
# Retrieve measurement counts.
counts = machine.result().get_counts()
print("Bell state |Phi+> counts:", counts)
# Expected: {'00': ~5000, '11': ~5000}, no '01' or '10'Expected output:
Bell state |Phi+> counts: {'00': 4973, '11': 5027}Getting the State Vector and Verifying Amplitudes
When you need to inspect the quantum state directly (rather than through measurement statistics), run the circuit without measurement and retrieve the state vector.
from pyqpanda3 import core
import numpy as np
# Build the Bell state circuit WITHOUT measurement.
prog = core.QProg()
prog << core.H(0)
prog << core.CNOT(0, 1)
# Run on CPUQVM. Without measurement, we get the full state vector.
machine = core.CPUQVM()
machine.run(prog, shots=1)
# Retrieve the state vector.
sv = machine.result().get_state_vector()
print("State vector:", sv)
# The Bell state |Phi+> should have amplitudes:
# |00>: 1/sqrt(2) ~ 0.7071
# |01>: 0
# |10>: 0
# |11>: 1/sqrt(2) ~ 0.7071
expected = np.array([1, 0, 0, 1]) / np.sqrt(2)
assert np.allclose(np.abs(sv), np.abs(expected), atol=1e-10), \
"State vector does not match expected Bell state amplitudes"
# Verify probabilities sum to 1.
probs = np.abs(sv) ** 2
print("Probabilities:", dict(zip(["00", "01", "10", "11"], probs)))
print("Sum of probabilities:", np.sum(probs))
# Expected: {'00': 0.5, '01': 0.0, '10': 0.0, '11': 0.5}, sum = 1.0Expected output:
State vector: [0.70710678+0.j 0.+0.j 0.+0.j 0.70710678+0.j]
Probabilities: {'00': 0.5, '01': 0.0, '10': 0.0, '11': 0.5}
Sum of probabilities: 1.0All Four Bell States
This example prepares each of the four Bell states and prints the measurement distribution so you can see how they differ.
from pyqpanda3 import core
def make_bell_state(variant):
"""Prepare one of the four Bell states.
Args:
variant: One of 'phi+', 'phi-', 'psi+', 'psi-'.
Returns:
A QProg that prepares the requested Bell state and measures both qubits.
"""
prog = core.QProg()
prog << core.H(0)
prog << core.CNOT(0, 1)
# Apply the appropriate Pauli correction to select the Bell state variant.
if variant == "phi+":
pass # No correction needed
elif variant == "phi-":
prog << core.Z(0)
elif variant == "psi+":
prog << core.X(0)
elif variant == "psi-":
# Y = iXZ, but in the computational basis Y = [[0,-i],[i,0]].
# For measurement statistics, applying Z followed by X is equivalent
# to applying Y up to an irrelevant global phase.
prog << core.Z(0)
prog << core.X(0)
else:
raise ValueError(f"Unknown Bell state variant: {variant}")
prog << core.measure([0, 1], [0, 1])
return prog
# Run all four variants and display the results.
machine = core.CPUQVM()
shots = 10000
for variant in ["phi+", "phi-", "psi+", "psi-"]:
prog = make_bell_state(variant)
machine.run(prog, shots=shots)
counts = machine.result().get_counts()
print(f"|{variant:>4s}> : {counts}")Expected output:
|phi+> : {'00': 4985, '11': 5015}
|phi-> : {'00': 5008, '11': 4992}
|psi+> : {'01': 5023, '10': 4977}
|psi-> : {'01': 4961, '10': 5039}Note on distinguishing 00 and 11 with equal probability. The relative phase (
Verifying Entanglement by Checking Correlation
Entanglement is verified by confirming that the two qubits are perfectly correlated. This example collects many shots and checks that no uncorrelated outcomes (01 or 10) appear.
from pyqpanda3 import core
# Prepare the Bell state |Phi+>.
prog = core.QProg()
prog << core.H(0)
prog << core.CNOT(0, 1)
prog << core.measure([0, 1], [0, 1])
machine = core.CPUQVM()
shots = 100000
machine.run(prog, shots=shots)
counts = machine.result().get_counts()
# The correlated outcomes.
correlated = counts.get("00", 0) + counts.get("11", 0)
# The uncorrelated outcomes (should be zero in a noiseless simulation).
uncorrelated = counts.get("01", 0) + counts.get("10", 0)
correlation_rate = correlated / shots
print(f"Correlated outcomes (00, 11): {correlated} / {shots} = {correlation_rate:.6f}")
print(f"Uncorrelated outcomes (01, 10): {uncorrelated}")
# In a noiseless simulation, the correlation should be exactly 1.0.
assert correlation_rate == 1.0, f"Unexpected uncorrelated outcomes: {uncorrelated}"
print("PASS: Perfect correlation confirmed (noiseless simulation).")Expected output:
Correlated outcomes (00, 11): 100000 / 100000 = 1.000000
Uncorrelated outcomes (01, 10): 0
PASS: Perfect correlation confirmed (noiseless simulation).Running on the DensityMatrixSimulator
The DensityMatrixSimulator is useful for analyzing the quantum state in detail -- purity, reduced density matrices, and exact probabilities -- without shot noise.
from pyqpanda3 import core
import numpy as np
# Build the Bell state circuit without measurement.
prog = core.QProg()
prog << core.H(0)
prog << core.CNOT(0, 1)
# Run on the density matrix simulator.
dm_sim = core.DensityMatrixSimulator()
dm_sim.run(prog)
# Get exact probabilities for each basis state.
probs = dm_sim.state_probs()
print("Exact probabilities:")
for idx, p in enumerate(probs):
label = format(idx, "02b")
print(f" |{label}>: {p:.6f}")
# Get the full density matrix (4x4 for 2 qubits).
dm = dm_sim.density_matrix()
print(f"\nDensity matrix shape: {dm.shape}")
print(f"Density matrix:\n{dm}")
# Verify purity. For a pure state, Tr(rho^2) = 1.0.
purity = np.trace(dm @ dm).real
print(f"\nPurity Tr(rho^2): {purity:.6f}")
assert abs(purity - 1.0) < 1e-10, "State is not pure!"
# Compute the reduced density matrix for qubit 0 by tracing out qubit 1.
# For a maximally entangled state, each subsystem is maximally mixed: I/2.
reduced_q0 = dm_sim.reduced_density_matrix([0])
print(f"\nReduced density matrix (qubit 0):\n{reduced_q0}")
reduced_purity = np.trace(reduced_q0 @ reduced_q0).real
print(f"Reduced purity: {reduced_purity:.6f} (maximally mixed = 0.5)")Expected output:
Exact probabilities:
|00>: 0.500000
|01>: 0.000000
|10>: 0.000000
|11>: 0.500000
Density matrix shape: (4, 4)
Density matrix:
[[0.5+0.j 0. +0.j 0. +0.j 0.5+0.j]
[0. +0.j 0. +0.j 0. +0.j 0. +0.j]
[0. +0.j 0. +0.j 0. +0.j 0. +0.j]
[0.5+0.j 0. +0.j 0. +0.j 0.5+0.j]]
Purity Tr(rho^2): 1.000000
Reduced density matrix (qubit 0):
[[0.5+0.j 0. +0.j]
[0. +0.j 0.5+0.j]]
Reduced purity: 0.500000 (maximally mixed = 0.5)The fact that the individual qubit is maximally mixed (
Running on the Stabilizer Simulator
The Bell state circuit uses only Clifford gates (H and CNOT), so it can be simulated efficiently on the Stabilizer backend. This is useful for rapid testing or for circuits with many qubits where full statevector simulation is expensive.
from pyqpanda3 import core
# The same Bell state circuit -- all gates are Clifford.
prog = core.QProg()
prog << core.H(0)
prog << core.CNOT(0, 1)
prog << core.measure([0, 1], [0, 1])
# The Stabilizer simulator tracks Pauli stabilizer generators instead of
# the full state vector. It runs in O(n^2) time per gate rather than O(2^n).
stab = core.Stabilizer()
stab.run(prog, shots=10000)
result = stab.result()
counts = result.get_counts()
print("Stabilizer Bell state counts:", counts)
# The results should match CPUQVM: only '00' and '11', roughly 50/50.Expected output:
Stabilizer Bell state counts: {'00': 4998, '11': 5002}Bell State with Noise Simulation
Real quantum hardware introduces errors. This example shows how a depolarizing noise model causes the previously forbidden outcomes (01, 10) to appear with small but nonzero probability.
from pyqpanda3 import core
# Build the Bell state circuit.
prog = core.QProg()
prog << core.H(0)
prog << core.CNOT(0, 1)
prog << core.measure([0, 1], [0, 1])
# Create a noise model with 2% depolarizing error on CNOT gates.
noise = core.NoiseModel()
noise.add_quantum_error(
core.depolarizing_error(0.02),
core.GateType.CNOT,
[0, 1]
)
# Add 1% depolarizing error on single-qubit gates.
noise.add_quantum_error(
core.depolarizing_error(0.01),
core.GateType.H,
[0]
)
noise.add_quantum_error(
core.depolarizing_error(0.01),
core.GateType.H,
[1]
)
# Run with noise on the CPUQVM.
machine = core.CPUQVM()
machine.run(prog, shots=10000, model=noise)
noisy_counts = machine.result().get_counts()
print("Noisy Bell state counts:", noisy_counts)
# Compute the Bell fidelity: fraction of outcomes that are correlated.
bell_fidelity = (noisy_counts.get("00", 0) + noisy_counts.get("11", 0)) / 10000
print(f"Bell fidelity: {bell_fidelity:.4f}")
# Also compare with the noiseless result.
machine.run(prog, shots=10000)
ideal_counts = machine.result().get_counts()
print(f"Ideal counts: {ideal_counts}")Expected output:
Noisy Bell state counts: {'00': 4872, '11': 4891, '01': 128, '10': 109}
Bell fidelity: 0.9763
Ideal counts: {'00': 4985, '11': 5015}The noise leaks probability into the 01 and 10 outcomes, reducing the Bell fidelity below 1.0. The amount of leakage depends on the noise strength.
Comparing Noise Channels on the Bell State
Different physical noise mechanisms affect the Bell state differently. This example applies several error channels and measures how each one degrades the Bell fidelity.
from pyqpanda3 import core
def bell_fidelity(noise_model, shots=10000):
"""Run a Bell state circuit with the given noise model and return fidelity."""
prog = core.QProg()
prog << core.H(0)
prog << core.CNOT(0, 1)
prog << core.measure([0, 1], [0, 1])
machine = core.CPUQVM()
machine.run(prog, shots=shots, model=noise_model)
counts = machine.result().get_counts()
correlated = counts.get("00", 0) + counts.get("11", 0)
return correlated / shots, counts
# Define noise models with different error channels at 3% strength.
error_rate = 0.03
channels = {
"Bit Flip (X)": core.pauli_x_error(error_rate),
"Phase Flip (Z)": core.pauli_z_error(error_rate),
"Depolarizing": core.depolarizing_error(error_rate),
"Amplitude Damping": core.amplitude_damping_error(error_rate),
"Phase Damping": core.phase_damping_error(error_rate),
}
print(f"{'Channel':<22s} {'Fidelity':>8s} {'Counts'}")
print("-" * 70)
for name, error in channels.items():
noise = core.NoiseModel()
noise.add_quantum_error(error, core.GateType.CNOT, [0, 1])
noise.add_quantum_error(error, core.GateType.H, [0])
noise.add_quantum_error(error, core.GateType.H, [1])
fid, counts = bell_fidelity(noise)
print(f"{name:<22s} {fid:>8.4f} {counts}")Expected output (approximate):
Channel Fidelity Counts
----------------------------------------------------------------------
Bit Flip (X) 0.9148 {'00': 4576, '11': 4572, '01': 424, '10': 428}
Phase Flip (Z) 0.9472 {'00': 4734, '11': 4738, '01': 264, '10': 264}
Depolarizing 0.8953 {'00': 4476, '11': 4477, '01': 526, '10': 521}
Amplitude Damping 0.9210 {'00': 4924, '11': 4286, '01': 410, '10': 380}
Phase Damping 0.9472 {'00': 4738, '11': 4734, '01': 264, '10': 264}Key observations:
- Depolarizing noise is the most destructive because it applies all three Pauli errors simultaneously.
- Amplitude damping is asymmetric:
gets more counts than because the error drives . - Phase flip (Z) does not change computational-basis measurement outcomes on a single qubit, but it still degrades the Bell fidelity because the CNOT converts phase errors into bit errors on the target qubit.
Exact Noise Analysis with DensityMatrixSimulator
For precise noise analysis without shot noise, use the DensityMatrixSimulator with a noise model.
from pyqpanda3 import core
import numpy as np
# Build the Bell state without measurement.
prog = core.QProg()
prog << core.H(0)
prog << core.CNOT(0, 1)
# Define a noise model with depolarizing error on CNOT.
noise = core.NoiseModel()
noise.add_quantum_error(
core.depolarizing_error(0.02),
core.GateType.CNOT,
[0, 1]
)
# Ideal simulation.
dm_ideal = core.DensityMatrixSimulator()
dm_ideal.run(prog)
ideal_probs = dm_ideal.state_probs()
ideal_dm = dm_ideal.density_matrix()
# Noisy simulation.
dm_noisy = core.DensityMatrixSimulator()
dm_noisy.run(prog, model=noise)
noisy_probs = dm_noisy.state_probs()
noisy_dm = dm_noisy.density_matrix()
# Compare exact probabilities.
print("Basis | Ideal | Noisy")
print("--------|-----------|----------")
for idx in range(4):
label = format(idx, "02b")
print(f" |{label}> | {ideal_probs[idx]:.6f} | {noisy_probs[idx]:.6f}")
# Compute purity.
ideal_purity = np.trace(ideal_dm @ ideal_dm).real
noisy_purity = np.trace(noisy_dm @ noisy_dm).real
print(f"\nIdeal purity: {ideal_purity:.6f}")
print(f"Noisy purity: {noisy_purity:.6f}")
print(f"Purity drop: {ideal_purity - noisy_purity:.6f}")
# Compute fidelity between ideal and noisy states.
# For a pure ideal state |psi>, F(|psi>, rho) = <psi|rho|psi>.
fidelity = np.real(ideal_dm[0, 0] * noisy_dm[0, 0]
+ ideal_dm[0, 3] * noisy_dm[3, 0]
+ ideal_dm[3, 0] * noisy_dm[0, 3]
+ ideal_dm[3, 3] * noisy_dm[3, 3])
print(f"Bell fidelity: {fidelity:.6f}")Expected output:
Basis | Ideal | Noisy
--------|-----------|----------
|00> | 0.500000 | 0.486667
|01> | 0.000000 | 0.013333
|10> | 0.000000 | 0.013333
|11> | 0.500000 | 0.486667
Ideal purity: 1.000000
Noisy purity: 0.973333
Purity drop: 0.026667
Bell fidelity: 0.973333Explanation
Why H Followed by CNOT Creates Entanglement
Let us trace through the circuit step by step, starting from
Step 1: Initial state.
Step 2: Apply Hadamard to qubit 0.
The Hadamard matrix is:
Applied to
So the joint state becomes:
At this point the qubits are not entangled. The state is a product state: each qubit has its own independent description.
Step 3: Apply CNOT with control=0, target=1.
The CNOT gate flips the target qubit if the control is
Therefore:
This state cannot be written as a product of two single-qubit states. The qubits are entangled.
Mathematical Derivation of Each Bell State
All four Bell states are derived by applying Pauli gates to
because
because
Since global phases are physically unobservable,
Measurement Statistics Interpretation
When you measure
The outcomes are perfectly correlated: measuring qubit 0 immediately tells you what qubit 1 will yield. This is quantified by the conditional probability:
This perfect correlation cannot be reproduced by any classical theory that respects locality (no faster-than-light communication). This is the content of Bell's theorem, experimentally confirmed by Aspect, Clauser, Zeilinger, and others (2022 Nobel Prize in Physics).
The Bell State as a Maximally Entangled State
A two-qubit state
For
Tracing out qubit B:
This means that if you look at either qubit alone, it appears completely random: 50% chance of
The concurrence and entropy of entanglement both reach their maximum values for Bell states:
| Measure | Formula | Bell State Value |
|---|---|---|
| Concurrence | 1 | |
| Entanglement entropy | 1 bit | |
| Purity of subsystem | 0.5 |
Connection to Quantum Teleportation
The Bell state is the quantum resource that enables teleportation. In the standard teleportation protocol:
- Alice and Bob share a Bell pair
. Alice holds qubit A; Bob holds qubit B. - Alice entangles her unknown state
with her half of the Bell pair using a CNOT and Hadamard. - Alice measures both of her qubits (C and A) and sends the 2-bit classical result to Bob.
- Bob applies a Pauli correction (I, X, Z, or Y) to his qubit B based on the 2 bits he receives. After the correction, Bob's qubit is in the state
.
Without the initial Bell pair, teleportation is impossible. The Bell pair provides the quantum channel (entanglement) that compensates for the fact that Alice only sends classical information.
Connection to Superdense Coding
Superdense coding is the reverse of teleportation: instead of sending one qubit to transmit two classical bits, you manipulate one qubit of a shared Bell pair to encode two classical bits, then send that one qubit.
The protocol:
- Alice and Bob share
. - Alice applies one of four Pauli operations to her qubit A:
: state remains -- encodes 00: state becomes -- encodes 01: state becomes -- encodes 10: state becomes -- encodes 11
- Alice sends her qubit A to Bob.
- Bob now holds both qubits. He applies a Bell measurement (reverse of the Bell state preparation circuit: CNOT then Hadamard, then measure) to distinguish which of the four Bell states he has, recovering the two classical bits.
The four Pauli operations map exactly to the four Bell states, which is why the ability to prepare and distinguish all four Bell states is essential for superdense coding.
Practical Tips
1. Always verify with statistics. A single shot tells you nothing. Run at least 1,000 shots to confirm the expected distribution. For quantitative fidelity estimates, use 10,000 or more.
2. Use DensityMatrixSimulator for analysis. When you need exact numbers (purity, reduced density matrices, von Neumann entropy), use the DensityMatrixSimulator instead of counting shots.
3. Use Stabilizer for large-scale Clifford circuits. If your Bell state is part of a larger Clifford circuit (e.g., an error correction code with hundreds of qubits), the Stabilizer backend can handle it efficiently.
4. Model noise realistically. When simulating for hardware predictions, use per-gate, per-qubit noise models with error rates calibrated from your target device. Typical values for superconducting hardware are 0.1% for single-qubit gates and 1-2% for two-qubit gates.
5. The Bell state circuit is the simplest entanglement test. If your hardware or simulator can correctly produce a 50/50 split between 00 and 11 with no 01 or 10, you have verified that the Hadamard and CNOT gates are working correctly.