""" Superdense Coding Protocol ========================== Transmits 2 classical bits using 1 qubit by leveraging a shared Bell pair. Superdense coding is the "dual" of quantum teleportation. Where teleportation uses 2 classical bits to send 1 qubit, superdense coding uses 1 qubit to send 2 classical bits. Both protocols consume one shared Bell pair. Category: Communication Difficulty: Beginner Framework: Qiskit Qubits: 2 Depth: 4 Gates: H, CX, X, Z Protocol overview: 1. Alice and Bob share a Bell pair |Phi+> = (|00> + |11>)/sqrt(2) 2. Alice encodes her 2-bit message by applying gates to her qubit: - 00 → I (identity, do nothing) → |Phi+> - 01 → X (bit flip) → |Psi+> - 10 → Z (phase flip) → |Phi-> - 11 → XZ (bit + phase flip) → |Psi-> 3. Alice sends her qubit to Bob (1 physical qubit transmitted) 4. Bob performs Bell measurement (reverse CNOT + H) to decode 2 bits Expected output: Each message decodes deterministically — Bob always recovers the exact 2-bit message with probability 1 (on a perfect simulator). References: - Bennett, Wiesner (1992). "Communication via One- and Two-Particle Operators on Einstein-Podolsky-Rosen States." Physical Review Letters 69(20), 2881-2884. - Nielsen & Chuang, Section 2.3 """ from qiskit import QuantumCircuit from qiskit_aer import AerSimulator # --------------------------------------------------------------------------- # Circuit construction # --------------------------------------------------------------------------- def create_superdense_coding(message: str = "00") -> QuantumCircuit: """Create a superdense coding circuit for a given 2-bit message. Protocol step-by-step: 1. Create Bell pair: H on qubit 0, CNOT(0,1) 2. Alice encodes message on qubit 0 using {I, X, Z, XZ} 3. Bob decodes: reverse CNOT(0,1), H on qubit 0, measure both Args: message: 2-bit string to encode ('00', '01', '10', '11'). Returns: QuantumCircuit implementing the full superdense coding protocol. """ if message not in ("00", "01", "10", "11"): raise ValueError(f"Message must be '00', '01', '10', or '11', got '{message}'") qc = QuantumCircuit(2, 2) # --- Step 1: Create Bell pair (shared resource) --- # Alice gets qubit 0, Bob gets qubit 1 qc.h(0) # Superposition on Alice's qubit qc.cx(0, 1) # Entangle with Bob's qubit: |Phi+> qc.barrier() # --- Step 2: Alice encodes her 2-bit message --- # Each gate transforms the Bell state to a different orthogonal state if message == "01": qc.x(0) # |Phi+> → |Psi+> (bit flip) elif message == "10": qc.z(0) # |Phi+> → |Phi-> (phase flip) elif message == "11": qc.x(0) # |Phi+> → |Psi-> (both flips) qc.z(0) # message "00": no gates needed (identity) qc.barrier() # --- Step 3: Bob decodes (reverse Bell circuit) --- # The reverse operation maps each Bell state back to a computational # basis state, making the message readable by standard measurement qc.cx(0, 1) # Reverse CNOT qc.h(0) # Reverse Hadamard qc.measure([0, 1], [0, 1]) # Read out the 2-bit message return qc # --------------------------------------------------------------------------- # Execution # --------------------------------------------------------------------------- def run_circuit(message: str = "00", shots: int = 1024) -> dict: """Execute superdense coding and return measurement counts. Args: message: 2-bit string to encode ('00', '01', '10', '11'). shots: Number of measurement repetitions (default: 1024). Returns: dict with 2-bit bitstring keys and count values. In ideal conditions, only the sent message appears. """ qc = create_superdense_coding(message) backend = AerSimulator() job = backend.run(qc, shots=shots) counts = dict(job.result().get_counts(qc)) return counts # --------------------------------------------------------------------------- # Verification # --------------------------------------------------------------------------- def verify_superdense_coding(shots: int = 4096) -> dict: """Verify that all four 2-bit messages are perfectly transmitted. For each message (00, 01, 10, 11), checks that Bob decodes the correct message with 100% probability (on a perfect simulator). Args: shots: Number of measurement repetitions per message. Returns: dict with keys: passed (bool), checks (list of per-message results). """ checks = [] all_passed = True for message in ["00", "01", "10", "11"]: counts = run_circuit(message=message, shots=shots) # Qiskit bitstrings are big-endian (highest qubit first), so we # reverse them to get the logical message order (bit 0 first) logical_counts = {bs[::-1]: c for bs, c in counts.items()} # Bob should decode exactly the sent message decoded = max(logical_counts, key=logical_counts.get) correct_count = logical_counts.get(message, 0) fidelity = correct_count / shots msg_passed = decoded == message and fidelity > 0.99 if not msg_passed: all_passed = False checks.append({ "name": f"message_{message}", "passed": msg_passed, "decoded": decoded, "fidelity": fidelity, "detail": ( f"Sent '{message}' → Decoded '{decoded}' " f"(fidelity={fidelity:.3f})" ), }) return { "passed": all_passed, "shots": shots, "checks": checks, } # --------------------------------------------------------------------------- # Main — interactive exploration # --------------------------------------------------------------------------- if __name__ == "__main__": print("Superdense Coding: Send 2 Classical Bits via 1 Qubit") print("=" * 55) for msg in ["00", "01", "10", "11"]: counts = run_circuit(msg, shots=1024) # Reverse Qiskit big-endian bitstrings to logical order logical_counts = {bs[::-1]: c for bs, c in counts.items()} decoded = max(logical_counts, key=logical_counts.get) print(f" Sent: {msg} → Decoded: {decoded} (counts: {logical_counts})") # Verification print("\nVerification (4096 shots per message):") result = verify_superdense_coding() for check in result["checks"]: symbol = "PASS" if check["passed"] else "FAIL" print(f" [{symbol}] {check['name']}: {check['detail']}") print(f"\nOverall: {'PASSED' if result['passed'] else 'FAILED'}")