# Quantum Circuits in PennyLane ## Table of Contents 1. [Basic Gates and Operations](#basic-gates-and-operations) 2. [Multi-Qubit Gates](#multi-qubit-gates) 3. [Controlled Operations](#controlled-operations) 4. [Measurements](#measurements) 5. [Circuit Construction Patterns](#circuit-construction-patterns) 6. [Dynamic Circuits](#dynamic-circuits) 7. [Circuit Inspection](#circuit-inspection) ## Basic Gates and Operations ### Single-Qubit Gates ```python import pennylane as qml # Pauli gates qml.PauliX(wires=0) # X gate (bit flip) qml.PauliY(wires=0) # Y gate qml.PauliZ(wires=0) # Z gate (phase flip) # Hadamard gate (superposition) qml.Hadamard(wires=0) # Phase gates qml.S(wires=0) # S gate (π/2 phase) qml.T(wires=0) # T gate (π/4 phase) qml.PhaseShift(phi, wires=0) # Arbitrary phase # Rotation gates (parameterized) qml.RX(theta, wires=0) # Rotation around X-axis qml.RY(theta, wires=0) # Rotation around Y-axis qml.RZ(theta, wires=0) # Rotation around Z-axis # General single-qubit rotation qml.Rot(phi, theta, omega, wires=0) # Universal gate (any single-qubit unitary) qml.U3(theta, phi, delta, wires=0) ``` ### Basis State Preparation ```python # Computational basis state qml.BasisState([1, 0, 1], wires=[0, 1, 2]) # |101⟩ # Amplitude encoding amplitudes = [0.5, 0.5, 0.5, 0.5] # Must be normalized qml.MottonenStatePreparation(amplitudes, wires=[0, 1]) ``` ## Multi-Qubit Gates ### Two-Qubit Gates ```python # CNOT (Controlled-NOT) qml.CNOT(wires=[0, 1]) # control=0, target=1 # CZ (Controlled-Z) qml.CZ(wires=[0, 1]) # SWAP gate qml.SWAP(wires=[0, 1]) # Controlled rotations qml.CRX(theta, wires=[0, 1]) qml.CRY(theta, wires=[0, 1]) qml.CRZ(theta, wires=[0, 1]) # Ising coupling gates qml.IsingXX(phi, wires=[0, 1]) qml.IsingYY(phi, wires=[0, 1]) qml.IsingZZ(phi, wires=[0, 1]) ``` ### Multi-Qubit Gates ```python # Toffoli gate (CCNOT) qml.Toffoli(wires=[0, 1, 2]) # control=0,1, target=2 # Multi-controlled X qml.MultiControlledX(control_wires=[0, 1, 2], wires=3) # Multi-qubit Pauli rotations qml.MultiRZ(theta, wires=[0, 1, 2]) ``` ## Controlled Operations ### General Controlled Operations ```python # Apply controlled version of any operation qml.ctrl(qml.RX(0.5, wires=1), control=0) # Multiple control qubits qml.ctrl(qml.RY(0.3, wires=2), control=[0, 1]) # Negative controls (activate when control is |0⟩) qml.ctrl(qml.Hadamard(wires=2), control=0, control_values=[0]) ``` ### Conditional Operations ```python @qml.qnode(dev) def conditional_circuit(): qml.Hadamard(wires=0) # Mid-circuit measurement m = qml.measure(0) # Apply gate conditionally qml.cond(m, qml.PauliX)(wires=1) return qml.expval(qml.PauliZ(1)) ``` ## Measurements ### Expectation Values ```python @qml.qnode(dev) def measure_expectation(): qml.Hadamard(wires=0) # Single observable return qml.expval(qml.PauliZ(0)) @qml.qnode(dev) def measure_tensor(): qml.Hadamard(wires=0) qml.Hadamard(wires=1) # Tensor product of observables return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) ``` ### Probability Distributions ```python @qml.qnode(dev) def measure_probabilities(): qml.Hadamard(wires=0) qml.CNOT(wires=[0, 1]) # Probabilities of all basis states return qml.probs(wires=[0, 1]) # Returns [p(|00⟩), p(|01⟩), p(|10⟩), p(|11⟩)] ``` ### Samples and Counts ```python @qml.qnode(dev) def measure_samples(shots=1000): qml.Hadamard(wires=0) # Raw samples return qml.sample(qml.PauliZ(0)) @qml.qnode(dev) def measure_counts(shots=1000): qml.Hadamard(wires=0) qml.CNOT(wires=[0, 1]) # Count occurrences return qml.counts(wires=[0, 1]) ``` ### Variance ```python @qml.qnode(dev) def measure_variance(): qml.RX(0.5, wires=0) # Variance of observable return qml.var(qml.PauliZ(0)) ``` ### Mid-Circuit Measurements ```python @qml.qnode(dev) def mid_circuit_measure(): qml.Hadamard(wires=0) # Measure qubit 0 during circuit m0 = qml.measure(0) # Use measurement result qml.cond(m0, qml.PauliX)(wires=1) # Final measurement return qml.expval(qml.PauliZ(1)) ``` ## Circuit Construction Patterns ### Layer-Based Construction ```python def layer(weights, wires): """Single layer of parameterized gates.""" for i, wire in enumerate(wires): qml.RY(weights[i], wires=wire) for wire in wires[:-1]: qml.CNOT(wires=[wire, wire+1]) @qml.qnode(dev) def layered_circuit(weights): n_layers = len(weights) wires = range(4) for i in range(n_layers): layer(weights[i], wires) return qml.expval(qml.PauliZ(0)) ``` ### Data Encoding ```python def angle_encoding(x, wires): """Encode classical data as rotation angles.""" for i, wire in enumerate(wires): qml.RX(x[i], wires=wire) def amplitude_encoding(x, wires): """Encode data as quantum state amplitudes.""" qml.MottonenStatePreparation(x, wires=wires) def basis_encoding(x, wires): """Encode binary data in computational basis.""" for i, val in enumerate(x): if val: qml.PauliX(wires=i) ``` ### Ansatz Patterns ```python # Hardware-efficient ansatz def hardware_efficient_ansatz(weights, wires): n_layers = len(weights) // len(wires) for layer in range(n_layers): # Rotation layer for i, wire in enumerate(wires): qml.RY(weights[layer * len(wires) + i], wires=wire) # Entanglement layer for wire in wires[:-1]: qml.CNOT(wires=[wire, wire+1]) # Alternating layered ansatz def alternating_ansatz(weights, wires): for w in weights: for wire in wires: qml.RX(w[wire], wires=wire) for wire in wires[:-1]: qml.CNOT(wires=[wire, wire+1]) ``` ## Dynamic Circuits ### For Loops ```python @qml.qnode(dev) def dynamic_for_loop(n_iterations): qml.Hadamard(wires=0) # Dynamic for loop for i in range(n_iterations): qml.RX(0.1 * i, wires=0) return qml.expval(qml.PauliZ(0)) ``` ### While Loops (with Catalyst) ```python @qml.qjit # Just-in-time compilation @qml.qnode(dev) def dynamic_while_loop(): qml.Hadamard(wires=0) # Dynamic while loop @qml.while_loop(lambda i: i < 5) def loop(i): qml.RX(0.1, wires=0) return i + 1 loop(0) return qml.expval(qml.PauliZ(0)) ``` ### Adaptive Circuits ```python @qml.qnode(dev) def adaptive_circuit(): qml.Hadamard(wires=0) # Measure and adapt m = qml.measure(0) # Different paths based on measurement if m: qml.RX(0.5, wires=1) else: qml.RY(0.5, wires=1) return qml.expval(qml.PauliZ(1)) ``` ## Circuit Inspection ### Drawing Circuits ```python # Text representation print(qml.draw(circuit)(params)) # ASCII art print(qml.draw(circuit, wire_order=[0,1,2])(params)) # Matplotlib visualization fig, ax = qml.draw_mpl(circuit)(params) ``` ### Analyzing Circuit Structure ```python # Get circuit specs specs = qml.specs(circuit)(params) print(f"Gates: {specs['gate_sizes']}") print(f"Depth: {specs['depth']}") print(f"Parameters: {specs['num_trainable_params']}") # Resource estimation resources = qml.resource.resource_estimation(circuit)(params) print(f"Total gates: {resources['num_gates']}") ``` ### Tape Inspection ```python # Record operations with qml.tape.QuantumTape() as tape: qml.Hadamard(wires=0) qml.CNOT(wires=[0, 1]) qml.expval(qml.PauliZ(0)) # Inspect tape contents print("Operations:", tape.operations) print("Measurements:", tape.measurements) print("Wires used:", tape.wires) ``` ### Circuit Transformations ```python # Expand composite operations expanded = qml.transforms.expand_tape(tape) # Cancel adjacent operations optimized = qml.transforms.cancel_inverses(tape) # Commute measurements to end commuted = qml.transforms.commute_controlled(tape) ``` ## Best Practices 1. **Use native gates** - Prefer gates supported by target device 2. **Minimize circuit depth** - Reduce decoherence effects 3. **Encode efficiently** - Choose encoding matching data structure 4. **Reuse circuits** - Cache compiled circuits when possible 5. **Validate measurements** - Ensure observables are Hermitian 6. **Check qubit count** - Verify device has sufficient wires 7. **Profile circuits** - Use `qml.specs()` to analyze complexity ## Common Patterns ### Bell State Preparation ```python @qml.qnode(dev) def bell_state(): qml.Hadamard(wires=0) qml.CNOT(wires=[0, 1]) return qml.state() # Returns |Φ+⟩ = (|00⟩ + |11⟩)/√2 ``` ### GHZ State ```python @qml.qnode(dev) def ghz_state(n_qubits): qml.Hadamard(wires=0) for i in range(n_qubits-1): qml.CNOT(wires=[0, i+1]) return qml.state() ``` ### Quantum Fourier Transform ```python def qft(wires): """Quantum Fourier Transform.""" n_wires = len(wires) for i in range(n_wires): qml.Hadamard(wires=wires[i]) for j in range(i+1, n_wires): qml.CRZ(np.pi / (2**(j-i)), wires=[wires[j], wires[i]]) ``` ### Inverse QFT ```python def inverse_qft(wires): """Inverse Quantum Fourier Transform.""" n_wires = len(wires) for i in range(n_wires-1, -1, -1): for j in range(n_wires-1, i, -1): qml.CRZ(-np.pi / (2**(j-i)), wires=[wires[j], wires[i]]) qml.Hadamard(wires=wires[i]) ```