# Simulation in Cirq This guide covers quantum circuit simulation, including exact and noisy simulations, parameter sweeps, and the Quantum Virtual Machine (QVM). ## Exact Simulation ### Basic Simulation ```python import cirq import numpy as np # Create circuit q0, q1 = cirq.LineQubit.range(2) circuit = cirq.Circuit( cirq.H(q0), cirq.CNOT(q0, q1), cirq.measure(q0, q1, key='result') ) # Simulate simulator = cirq.Simulator() result = simulator.run(circuit, repetitions=1000) # Get measurement results print(result.histogram(key='result')) ``` ### State Vector Simulation ```python # Simulate without measurement to get final state simulator = cirq.Simulator() result = simulator.simulate(circuit_without_measurement) # Access state vector state_vector = result.final_state_vector print(f"State vector: {state_vector}") # Get amplitudes print(f"Amplitude of |00⟩: {state_vector[0]}") print(f"Amplitude of |11⟩: {state_vector[3]}") ``` ### Density Matrix Simulation ```python # Use density matrix simulator for mixed states simulator = cirq.DensityMatrixSimulator() result = simulator.simulate(circuit) # Access density matrix density_matrix = result.final_density_matrix print(f"Density matrix shape: {density_matrix.shape}") ``` ### Step-by-Step Simulation ```python # Simulate moment-by-moment simulator = cirq.Simulator() for step in simulator.simulate_moment_steps(circuit): print(f"State after moment {step.moment}: {step.state_vector()}") ``` ## Sampling and Measurements ### Run Multiple Shots ```python # Run circuit multiple times result = simulator.run(circuit, repetitions=10000) # Access measurement counts counts = result.histogram(key='result') print(f"Measurement counts: {counts}") # Get raw measurements measurements = result.measurements['result'] print(f"Shape: {measurements.shape}") # (repetitions, num_qubits) ``` ### Expectation Values ```python # Measure observable expectation value from cirq import PauliString observable = PauliString({q0: cirq.Z, q1: cirq.Z}) result = simulator.simulate_expectation_values( circuit, observables=[observable] ) print(f"⟨ZZ⟩ = {result[0]}") ``` ## Parameter Sweeps ### Sweep Over Parameters ```python import sympy # Create parameterized circuit theta = sympy.Symbol('theta') q = cirq.LineQubit(0) circuit = cirq.Circuit( cirq.ry(theta)(q), cirq.measure(q, key='m') ) # Define parameter sweep sweep = cirq.Linspace(key='theta', start=0, stop=2*np.pi, length=50) # Run sweep simulator = cirq.Simulator() results = simulator.run_sweep(circuit, params=sweep, repetitions=1000) # Process results for params, result in zip(sweep, results): theta_val = params['theta'] counts = result.histogram(key='m') print(f"θ={theta_val:.2f}: {counts}") ``` ### Multiple Parameters ```python # Sweep over multiple parameters theta = sympy.Symbol('theta') phi = sympy.Symbol('phi') circuit = cirq.Circuit( cirq.ry(theta)(q0), cirq.rz(phi)(q1) ) # Product sweep (all combinations) sweep = cirq.Product( cirq.Linspace('theta', 0, np.pi, 10), cirq.Linspace('phi', 0, 2*np.pi, 10) ) results = simulator.run_sweep(circuit, params=sweep, repetitions=100) ``` ### Zip Sweep (Paired Parameters) ```python # Sweep parameters together sweep = cirq.Zip( cirq.Linspace('theta', 0, np.pi, 20), cirq.Linspace('phi', 0, 2*np.pi, 20) ) results = simulator.run_sweep(circuit, params=sweep, repetitions=100) ``` ## Noisy Simulation ### Adding Noise Channels ```python # Create noisy circuit noisy_circuit = circuit.with_noise(cirq.depolarize(p=0.01)) # Simulate noisy circuit simulator = cirq.DensityMatrixSimulator() result = simulator.run(noisy_circuit, repetitions=1000) ``` ### Custom Noise Models ```python # Apply different noise to different gates noise_model = cirq.NoiseModel.from_noise_model_like( cirq.ConstantQubitNoiseModel(cirq.depolarize(0.01)) ) # Simulate with noise model result = cirq.DensityMatrixSimulator(noise=noise_model).run( circuit, repetitions=1000 ) ``` See `noise.md` for comprehensive noise modeling details. ## State Histograms ### Visualize Results ```python import matplotlib.pyplot as plt # Get histogram result = simulator.run(circuit, repetitions=1000) counts = result.histogram(key='result') # Plot plt.bar(counts.keys(), counts.values()) plt.xlabel('State') plt.ylabel('Counts') plt.title('Measurement Results') plt.show() ``` ### State Probability Distribution ```python # Get state vector result = simulator.simulate(circuit_without_measurement) state_vector = result.final_state_vector # Compute probabilities probabilities = np.abs(state_vector) ** 2 # Plot plt.bar(range(len(probabilities)), probabilities) plt.xlabel('Basis State Index') plt.ylabel('Probability') plt.show() ``` ## Quantum Virtual Machine (QVM) QVM simulates realistic quantum hardware with device-specific constraints and noise. ### Using Virtual Devices ```python # Use a virtual Google device import cirq_google # Get virtual device device = cirq_google.Sycamore # Create circuit on device qubits = device.metadata.qubit_set circuit = cirq.Circuit(device=device) # Add operations respecting device constraints circuit.append(cirq.CZ(qubits[0], qubits[1])) # Validate circuit against device device.validate_circuit(circuit) ``` ### Noisy Virtual Hardware ```python # Simulate with device noise processor = cirq_google.get_engine().get_processor('weber') noise_props = processor.get_device_specification() # Create realistic noisy simulator noisy_sim = cirq.DensityMatrixSimulator( noise=cirq_google.NoiseModelFromGoogleNoiseProperties(noise_props) ) result = noisy_sim.run(circuit, repetitions=1000) ``` ## Advanced Simulation Techniques ### Custom Initial State ```python # Start from custom state initial_state = np.array([1, 0, 0, 1]) / np.sqrt(2) # |00⟩ + |11⟩ simulator = cirq.Simulator() result = simulator.simulate(circuit, initial_state=initial_state) ``` ### Partial Trace ```python # Trace out subsystems result = simulator.simulate(circuit) full_state = result.final_state_vector # Compute reduced density matrix for first qubit from cirq import partial_trace reduced_dm = partial_trace(result.final_density_matrix, keep_indices=[0]) ``` ### Intermediate State Access ```python # Get state at specific moment simulator = cirq.Simulator() for i, step in enumerate(simulator.simulate_moment_steps(circuit)): if i == 5: # After 5th moment state = step.state_vector() print(f"State after moment 5: {state}") break ``` ## Simulation Performance ### Optimizing Large Simulations 1. **Use state vector for pure states**: Faster than density matrix 2. **Avoid density matrix when possible**: Exponentially more expensive 3. **Batch parameter sweeps**: More efficient than individual runs 4. **Use appropriate repetitions**: Balance accuracy vs computation time ```python # Efficient: Single sweep results = simulator.run_sweep(circuit, params=sweep, repetitions=100) # Inefficient: Multiple individual runs results = [simulator.run(circuit, param_resolver=p, repetitions=100) for p in sweep] ``` ### Memory Considerations ```python # For large systems, monitor state vector size n_qubits = 20 state_size = 2**n_qubits * 16 # bytes (complex128) print(f"State vector size: {state_size / 1e9:.2f} GB") ``` ## Stabilizer Simulation For circuits with only Clifford gates, use efficient stabilizer simulation: ```python # Clifford circuit (H, S, CNOT) circuit = cirq.Circuit( cirq.H(q0), cirq.S(q1), cirq.CNOT(q0, q1) ) # Use stabilizer simulator (exponentially faster) simulator = cirq.CliffordSimulator() result = simulator.run(circuit, repetitions=1000) ``` ## Best Practices 1. **Choose appropriate simulator**: Use Simulator for pure states, DensityMatrixSimulator for mixed states 2. **Use parameter sweeps**: More efficient than running individual circuits 3. **Validate circuits**: Check circuit validity before long simulations 4. **Monitor resource usage**: Track memory for large-scale simulations 5. **Use stabilizer simulation**: When circuits contain only Clifford gates 6. **Save intermediate results**: For long parameter sweeps or optimization runs