Skip to content

Cirq Backend Integration

Cirq is Google's open-source quantum computing framework that focuses on near-term quantum devices and NISQ algorithms. SuperQuantX integrates with Cirq to provide access to Google's quantum ecosystem and advanced circuit optimization features.

Overview

The Cirq backend in SuperQuantX offers:

  • Google Quantum Hardware: Access to Google's quantum processors
  • Advanced Circuit Optimization: NISQ-optimized circuit compilation
  • Flexible Circuit Construction: Pythonic circuit building with moments
  • Noise Modeling: Comprehensive noise simulation capabilities
  • Custom Gates: Easy creation of custom quantum operations

Installation

# Install SuperQuantX with Cirq support
pip install superquantx[cirq]

# Or install Cirq directly
pip install cirq cirq-google cirq-web

Quick Start

Basic Usage

import superquantx as sqx

# Get Cirq backend
backend = sqx.get_backend('cirq')
print(f"Backend: {backend}")

# Create Bell state circuit
circuit = backend.create_circuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.measure_all()

# Execute circuit
result = backend.run(circuit, shots=1000)
print(f"Bell state results: {result.get_counts()}")

Google Quantum Hardware

# Note: Requires Google Quantum AI access
# This is for demonstration - actual access requires approval

# Connect to Google quantum processor
try:
    import cirq_google

    # Get Google quantum engine service
    engine = cirq_google.get_engine()

    # Use Google processor
    backend = sqx.CirqBackend(
        processor_id='rainbow',  # Example processor
        shots=1000
    )

    # Execute on Google hardware
    result = backend.run(circuit, shots=1000)
    print(f"Google quantum result: {result.get_counts()}")

except Exception as e:
    print(f"Google Quantum access requires setup: {e}")

Configuration Options

Simulator Configuration

# Different Cirq simulators
simulators = {
    'simulator': 'Basic state vector simulator',
    'density_matrix_simulator': 'Density matrix with noise support',
    'sparse_simulator': 'Sparse state vector simulator',
    'clifford_simulator': 'Efficient Clifford simulation',
}

# Create backend with specific simulator
backend = sqx.CirqBackend(
    simulator='density_matrix_simulator',
    shots=2048,
    seed=42
)

# Custom simulator configuration
backend = sqx.CirqBackend(
    simulator='simulator',
    shots=1000,
    noise_model=None,      # Custom noise model
    initial_state=None,    # Custom initial state
    qubit_order=None       # Custom qubit ordering
)

Advanced Configuration

import cirq
import numpy as np

# Custom qubit layout (Google Sycamore-like)
def create_sycamore_qubits(rows=5, cols=6):
    """Create Sycamore-like qubit layout."""
    qubits = []
    for row in range(rows):
        for col in range(cols):
            # Skip some qubits to match Sycamore pattern
            if (row % 2 == 0 and col % 2 == 1) or (row % 2 == 1 and col % 2 == 0):
                continue
            qubits.append(cirq.GridQubit(row, col))
    return qubits

# Create backend with custom qubits
custom_qubits = create_sycamore_qubits(3, 4)
backend = sqx.CirqBackend(
    qubits=custom_qubits,
    simulator='simulator',
    shots=1000
)

print(f"Custom qubit layout: {len(custom_qubits)} qubits")

Working with Circuits

Circuit Construction

def demonstrate_cirq_circuits():
    """Show Cirq circuit construction features."""

    backend = sqx.get_backend('cirq')

    # Create circuit with 4 qubits
    circuit = backend.create_circuit(4)

    # Basic single-qubit gates
    circuit.h(0)           # Hadamard
    circuit.x(1)           # Pauli-X
    circuit.y(2)           # Pauli-Y
    circuit.z(3)           # Pauli-Z

    # Rotation gates
    circuit.rx(np.pi/4, 0) # X rotation
    circuit.ry(np.pi/3, 1) # Y rotation
    circuit.rz(np.pi/6, 2) # Z rotation

    # Phase gates
    circuit.s(0)           # S gate
    circuit.t(1)           # T gate

    # Two-qubit gates
    circuit.cx(0, 1)       # CNOT
    circuit.cz(1, 2)       # Controlled-Z
    circuit.swap(2, 3)     # SWAP

    # Controlled operations
    circuit.cz(0, 3)       # Controlled-Z

    # Add measurement
    circuit.measure_all()

    # Execute circuit
    result = backend.run(circuit, shots=1000)
    print(f"Cirq circuit results: {result.get_counts()}")

    # Display circuit structure (if available)
    try:
        print("Circuit structure:")
        print(circuit)
    except:
        print("Circuit visualization not available")

demonstrate_cirq_circuits()

Moment-Based Circuit Construction

def cirq_moment_construction():
    """Demonstrate Cirq's moment-based circuit construction."""

    import cirq

    backend = sqx.get_backend('cirq')

    # Create qubits
    qubits = [cirq.GridQubit(0, i) for i in range(4)]

    # Build circuit using Cirq's native moment system
    cirq_circuit = cirq.Circuit()

    # Moment 1: Initialize all qubits in superposition
    cirq_circuit.append([
        cirq.H(qubits[0]),
        cirq.H(qubits[1]),
        cirq.H(qubits[2]),
        cirq.H(qubits[3])
    ])

    # Moment 2: Entangling layer
    cirq_circuit.append([
        cirq.CZ(qubits[0], qubits[1]),
        cirq.CZ(qubits[2], qubits[3])
    ])

    # Moment 3: Second entangling layer
    cirq_circuit.append([
        cirq.CZ(qubits[1], qubits[2])
    ])

    # Moment 4: Parameterized rotations
    angles = np.random.random(4) * 2 * np.pi
    cirq_circuit.append([
        cirq.rz(angles[0])(qubits[0]),
        cirq.rz(angles[1])(qubits[1]),
        cirq.rz(angles[2])(qubits[2]),
        cirq.rz(angles[3])(qubits[3])
    ])

    # Add measurements
    cirq_circuit.append([
        cirq.measure(qubit, key=f'q{i}')
        for i, qubit in enumerate(qubits)
    ])

    print("Moment-based circuit:")
    print(f"Circuit depth: {len(cirq_circuit)}")
    print(f"Number of moments: {len(cirq_circuit.moments)}")

    # Execute using Cirq simulator directly
    simulator = cirq.Simulator()
    result = simulator.run(cirq_circuit, repetitions=1000)

    # Convert to SuperQuantX format
    counts = {}
    for i in range(1000):
        measurement = ''.join([
            str(result.measurements[f'q{j}'][i][0])
            for j in range(4)
        ])
        counts[measurement] = counts.get(measurement, 0) + 1

    print(f"Execution results: {dict(list(counts.items())[:5])}...")

    # Analyze circuit structure
    print(f"\nCircuit Analysis:")
    print(f"Total operations: {len(list(cirq_circuit.all_operations()))}")
    print(f"Two-qubit gates: {len([op for op in cirq_circuit.all_operations() if len(op.qubits) == 2])}")
    print(f"Single-qubit gates: {len([op for op in cirq_circuit.all_operations() if len(op.qubits) == 1])}")

cirq_moment_construction()

Custom Gate Implementation

def cirq_custom_gates():
    """Demonstrate custom gate creation in Cirq."""

    import cirq

    # Define custom single-qubit gate
    class CustomRotation(cirq.Gate):
        """Custom parameterized rotation gate."""

        def __init__(self, theta, phi):
            self.theta = theta
            self.phi = phi

        def _num_qubits_(self):
            return 1

        def _unitary_(self):
            c = np.cos(self.theta / 2)
            s = np.sin(self.theta / 2)
            return np.array([
                [c, -s * np.exp(1j * self.phi)],
                [s, c * np.exp(1j * self.phi)]
            ])

        def _circuit_diagram_info_(self, args):
            return f'CustomRot(θ={self.theta:.2f}, φ={self.phi:.2f})'

    # Define custom two-qubit gate
    class CustomEntangling(cirq.Gate):
        """Custom entangling gate."""

        def __init__(self, angle):
            self.angle = angle

        def _num_qubits_(self):
            return 2

        def _unitary_(self):
            # Parametric entangling gate
            c = np.cos(self.angle)
            s = np.sin(self.angle)
            return np.array([
                [1, 0, 0, 0],
                [0, c, 1j*s, 0],
                [0, 1j*s, c, 0],
                [0, 0, 0, 1]
            ])

        def _circuit_diagram_info_(self, args):
            return ['CustomEnt'] * 2

    # Use custom gates in circuit
    qubits = [cirq.GridQubit(0, i) for i in range(3)]
    circuit = cirq.Circuit()

    # Apply custom single-qubit gates
    circuit.append([
        CustomRotation(np.pi/4, np.pi/3)(qubits[0]),
        CustomRotation(np.pi/6, np.pi/4)(qubits[1]),
        CustomRotation(np.pi/8, np.pi/2)(qubits[2])
    ])

    # Apply custom entangling gates
    circuit.append([
        CustomEntangling(np.pi/5)(qubits[0], qubits[1]),
        CustomEntangling(np.pi/7)(qubits[1], qubits[2])
    ])

    # Add measurements
    circuit.append(cirq.measure_each(*qubits))

    print("Custom Gate Circuit:")
    print(circuit)

    # Execute circuit
    simulator = cirq.Simulator()
    result = simulator.run(circuit, repetitions=1000)

    # Analyze results
    measurement_counts = {}
    for i in range(1000):
        measurement = ''.join([str(result.measurements[f'{qubit}'][i][0]) for qubit in qubits])
        measurement_counts[measurement] = measurement_counts.get(measurement, 0) + 1

    print(f"\nCustom gate circuit results:")
    for outcome, count in sorted(measurement_counts.items()):
        probability = count / 1000
        print(f"  |{outcome}⟩: {count:3d} ({probability:.3f})")

cirq_custom_gates()

Google Quantum Hardware Integration

Sycamore Processor Emulation

def sycamore_emulation():
    """Emulate Google's Sycamore quantum processor."""

    import cirq
    import cirq_google

    # Create Sycamore-like device
    def create_sycamore_device():
        """Create a device mimicking Google's Sycamore."""
        # Simplified Sycamore layout (5x5 subset)
        qubits = []
        for row in range(5):
            for col in range(5):
                # Create checkerboard pattern like Sycamore
                if (row + col) % 2 == 0:
                    qubits.append(cirq.GridQubit(row, col))

        # Define connectivity (nearest neighbors only)
        pairs = []
        for qubit in qubits:
            row, col = qubit.row, qubit.col
            # Check all four directions
            for dr, dc in [(0, 1), (1, 0), (0, -1), (-1, 0)]:
                neighbor = cirq.GridQubit(row + dr, col + dc)
                if neighbor in qubits and (qubit, neighbor) not in pairs and (neighbor, qubit) not in pairs:
                    pairs.append((qubit, neighbor))

        return qubits, pairs

    qubits, connectivity = create_sycamore_device()

    print(f"Sycamore emulation:")
    print(f"  Qubits: {len(qubits)}")
    print(f"  Connections: {len(connectivity)}")

    # Create quantum supremacy-inspired circuit
    def create_supremacy_circuit(depth=12):
        """Create a quantum supremacy-style random circuit."""
        circuit = cirq.Circuit()

        # Single-qubit gates (Sycamore gateset)
        single_gates = [cirq.X**0.5, cirq.Y**0.5, cirq.T]

        for layer in range(depth):
            # Single-qubit layer
            operations = []
            for qubit in qubits:
                gate = np.random.choice(single_gates)
                operations.append(gate(qubit))
            circuit.append(operations)

            # Two-qubit layer (use subset of connections)
            two_qubit_ops = []
            used_qubits = set()

            # Randomly select connections that don't conflict
            available_pairs = [pair for pair in connectivity 
                             if pair[0] not in used_qubits and pair[1] not in used_qubits]

            np.random.shuffle(available_pairs)

            for pair in available_pairs[:len(available_pairs)//2]:  # Use half the connections
                # Use fSim gate (approximating Google's hardware)
                theta = np.pi/2  # Typical fSim parameter
                phi = np.pi/6

                # Approximate fSim with available gates
                fsim_ops = [
                    cirq.CZ(*pair),
                    cirq.rz(phi)(pair[0]),
                    cirq.rz(phi)(pair[1])
                ]
                two_qubit_ops.extend(fsim_ops)

                used_qubits.update(pair)

            if two_qubit_ops:
                circuit.append(two_qubit_ops)

        # Add measurements
        circuit.append(cirq.measure_each(*qubits))

        return circuit

    # Generate and execute supremacy-style circuit
    supremacy_circuit = create_supremacy_circuit(depth=8)  # Reduced depth for demo

    print(f"\nQuantum Supremacy Circuit:")
    print(f"  Depth: {len(supremacy_circuit)}")
    print(f"  Operations: {len(list(supremacy_circuit.all_operations()))}")

    # Execute with limited shots due to complexity
    simulator = cirq.Simulator()
    result = simulator.run(supremacy_circuit, repetitions=100)  # Reduced shots

    # Analyze output distribution
    measurement_counts = {}
    for i in range(100):
        # Get measurement result
        measurement = ''.join([
            str(result.measurements[f'{qubit}'][i][0])
            for qubit in qubits
        ])
        measurement_counts[measurement] = measurement_counts.get(measurement, 0) + 1

    print(f"\nOutput Analysis:")
    print(f"  Unique outcomes: {len(measurement_counts)}")
    print(f"  Max probability: {max(measurement_counts.values())/100:.3f}")
    print(f"  Min probability: {min(measurement_counts.values())/100:.3f}")

    # Check for quantum supremacy signature (Porter-Thomas distribution)
    probabilities = np.array(list(measurement_counts.values())) / 100
    mean_prob = np.mean(probabilities)
    variance = np.var(probabilities)

    print(f"  Mean probability: {mean_prob:.6f}")
    print(f"  Variance: {variance:.6f}")
    print(f"  Expected variance (Porter-Thomas): {mean_prob**2:.6f}")

sycamore_emulation()

Noise Modeling

Comprehensive Noise Models

def cirq_noise_modeling():
    """Demonstrate comprehensive noise modeling in Cirq."""

    import cirq

    # Create noise models
    def create_device_noise_model():
        """Create realistic device noise model."""

        # Single-qubit depolarizing noise
        single_qubit_error = cirq.depolarize(p=0.001)  # 0.1% error rate

        # Two-qubit depolarizing noise  
        two_qubit_error = cirq.depolarize(p=0.01)      # 1% error rate

        # Readout errors
        readout_error = cirq.BitFlipChannel(p=0.02)    # 2% readout error

        # Amplitude damping (T1 decay)
        t1_error = cirq.AmplitudeDampingChannel(gamma=0.001)  # T1 ~ 100μs, gate ~ 25ns

        # Phase damping (T2 dephasing)  
        t2_error = cirq.PhaseDampingChannel(gamma=0.0005)     # T2 ~ 70μs

        return {
            'single_qubit_gates': [single_qubit_error, t1_error, t2_error],
            'two_qubit_gates': [two_qubit_error, t1_error, t2_error],
            'readout': [readout_error]
        }

    noise_model = create_device_noise_model()

    # Create test circuit
    qubits = [cirq.GridQubit(0, i) for i in range(3)]
    circuit = cirq.Circuit()

    # Bell state preparation
    circuit.append([
        cirq.H(qubits[0]),
        cirq.CZ(qubits[0], qubits[1]),
        cirq.CZ(qubits[1], qubits[2])
    ])

    circuit.append(cirq.measure_each(*qubits))

    print("Noise Model Testing")
    print("=" * 20)

    # Test without noise
    noiseless_simulator = cirq.Simulator()
    noiseless_result = noiseless_simulator.run(circuit, repetitions=1000)

    noiseless_counts = {}
    for i in range(1000):
        measurement = ''.join([str(noiseless_result.measurements[f'{qubit}'][i][0]) for qubit in qubits])
        noiseless_counts[measurement] = noiseless_counts.get(measurement, 0) + 1

    print("Noiseless simulation:")
    for outcome, count in sorted(noiseless_counts.items()):
        print(f"  |{outcome}⟩: {count:3d} ({count/1000:.3f})")

    # Test with noise
    noisy_circuit = circuit.copy()

    # Add noise after each gate
    def add_noise_to_circuit(circuit, noise_model):
        noisy_circuit = cirq.Circuit()

        for moment in circuit:
            # Add original operations
            noisy_circuit.append(moment)

            # Add noise after each operation
            noise_ops = []
            for op in moment:
                if len(op.qubits) == 1:  # Single-qubit gate
                    for noise_channel in noise_model['single_qubit_gates']:
                        noise_ops.append(noise_channel.on(*op.qubits))
                elif len(op.qubits) == 2:  # Two-qubit gate
                    for noise_channel in noise_model['two_qubit_gates']:
                        for qubit in op.qubits:
                            noise_ops.append(noise_channel.on(qubit))

            if noise_ops:
                noisy_circuit.append(noise_ops)

        return noisy_circuit

    # Apply noise (simplified approach)
    noisy_simulator = cirq.DensityMatrixSimulator()

    # Create noisy circuit manually for demonstration
    noisy_test_circuit = cirq.Circuit()

    # Bell preparation with noise
    noisy_test_circuit.append(cirq.H(qubits[0]))
    noisy_test_circuit.append(cirq.depolarize(0.001).on(qubits[0]))

    noisy_test_circuit.append(cirq.CZ(qubits[0], qubits[1]))
    noisy_test_circuit.append([
        cirq.depolarize(0.01).on(qubits[0]),
        cirq.depolarize(0.01).on(qubits[1])
    ])

    noisy_test_circuit.append(cirq.CZ(qubits[1], qubits[2]))
    noisy_test_circuit.append([
        cirq.depolarize(0.01).on(qubits[1]),
        cirq.depolarize(0.01).on(qubits[2])
    ])

    noisy_test_circuit.append(cirq.measure_each(*qubits))

    noisy_result = noisy_simulator.run(noisy_test_circuit, repetitions=1000)

    noisy_counts = {}
    for i in range(1000):
        measurement = ''.join([str(noisy_result.measurements[f'{qubit}'][i][0]) for qubit in qubits])
        noisy_counts[measurement] = noisy_counts.get(measurement, 0) + 1

    print("\nNoisy simulation:")
    for outcome, count in sorted(noisy_counts.items()):
        print(f"  |{outcome}⟩: {count:3d} ({count/1000:.3f})")

    # Compare fidelities
    def calculate_fidelity(counts1, counts2, total_shots):
        """Calculate fidelity between two probability distributions."""
        all_outcomes = set(counts1.keys()) | set(counts2.keys())
        fidelity = 0
        for outcome in all_outcomes:
            p1 = counts1.get(outcome, 0) / total_shots
            p2 = counts2.get(outcome, 0) / total_shots
            fidelity += np.sqrt(p1 * p2)
        return fidelity

    fidelity = calculate_fidelity(noiseless_counts, noisy_counts, 1000)
    print(f"\nFidelity between noiseless and noisy: {fidelity:.3f}")

    # Analyze error types
    error_states = ['001', '010', '100', '011', '101', '110']  # Non-computational states
    total_errors = sum(noisy_counts.get(state, 0) for state in error_states)
    error_rate = total_errors / 1000

    print(f"Total error rate: {error_rate:.3f}")

cirq_noise_modeling()

Advanced Features

Circuit Optimization

def cirq_circuit_optimization():
    """Demonstrate Cirq's circuit optimization capabilities."""

    import cirq

    print("Circuit Optimization with Cirq")
    print("=" * 32)

    # Create unoptimized circuit
    qubits = [cirq.GridQubit(0, i) for i in range(4)]
    unoptimized_circuit = cirq.Circuit()

    # Add redundant operations that can be optimized
    unoptimized_circuit.append([
        cirq.H(qubits[0]),
        cirq.X(qubits[0]),  # H-X can be optimized
        cirq.H(qubits[0]),  # H-X-H = Z-H

        cirq.Y(qubits[1]),
        cirq.Y(qubits[1]),  # Y-Y = I (cancels out)

        cirq.CZ(qubits[0], qubits[1]),
        cirq.CZ(qubits[0], qubits[1]),  # CZ-CZ = I

        cirq.SWAP(qubits[2], qubits[3]),
        cirq.SWAP(qubits[2], qubits[3]),  # SWAP-SWAP = I
    ])

    unoptimized_circuit.append(cirq.measure_each(*qubits))

    print("Original circuit:")
    print(f"  Moments: {len(unoptimized_circuit)}")
    print(f"  Operations: {len(list(unoptimized_circuit.all_operations()))}")

    # Apply various optimization strategies
    optimizers = [
        ("Merge Single-Qubit Gates", cirq.MergeSingleQubitGates()),
        ("Drop Empty Moments", cirq.DropEmptyMoments()),
        ("Drop Negligible Operations", cirq.DropNegligible()),
        ("Eject Z", cirq.EjectZ()),
        ("Eject PhasedPaulis", cirq.EjectPhasedPaulis()),
    ]

    current_circuit = unoptimized_circuit.copy()

    for name, optimizer in optimizers:
        try:
            optimizer.optimize_circuit(current_circuit)
            print(f"\nAfter {name}:")
            print(f"  Moments: {len(current_circuit)}")
            print(f"  Operations: {len(list(current_circuit.all_operations()))}")
        except Exception as e:
            print(f"  {name} failed: {e}")

    # Compare execution results
    simulator = cirq.Simulator()

    # Execute original circuit
    original_result = simulator.run(unoptimized_circuit, repetitions=1000)
    original_counts = {}
    for i in range(1000):
        measurement = ''.join([str(original_result.measurements[f'{qubit}'][i][0]) for qubit in qubits])
        original_counts[measurement] = original_counts.get(measurement, 0) + 1

    # Execute optimized circuit
    optimized_result = simulator.run(current_circuit, repetitions=1000)
    optimized_counts = {}
    for i in range(1000):
        measurement = ''.join([str(optimized_result.measurements[f'{qubit}'][i][0]) for qubit in qubits])
        optimized_counts[measurement] = optimized_counts.get(measurement, 0) + 1

    print(f"\nExecution Results Comparison:")
    print(f"Original circuit outcomes: {len(original_counts)}")
    print(f"Optimized circuit outcomes: {len(optimized_counts)}")

    # Check if results are equivalent
    outcomes_match = (set(original_counts.keys()) == set(optimized_counts.keys()))
    print(f"Outcomes match: {outcomes_match}")

    if outcomes_match:
        max_diff = max(abs(original_counts.get(outcome, 0) - optimized_counts.get(outcome, 0)) 
                      for outcome in original_counts.keys())
        print(f"Maximum count difference: {max_diff}")

cirq_circuit_optimization()

Quantum Algorithms Implementation

def cirq_quantum_algorithms():
    """Implement quantum algorithms using Cirq backend."""

    import cirq

    print("Quantum Algorithms with Cirq")
    print("=" * 30)

    # 1. Quantum Fourier Transform
    def qft_cirq(qubits):
        """Implement QFT using Cirq."""
        n = len(qubits)
        circuit = cirq.Circuit()

        for i in range(n):
            circuit.append(cirq.H(qubits[i]))

            for j in range(i + 1, n):
                # Controlled phase rotation
                angle = np.pi / (2 ** (j - i))
                circuit.append(cirq.CZ(qubits[i], qubits[j]) ** (angle / np.pi))

        # Reverse qubit order
        for i in range(n // 2):
            circuit.append(cirq.SWAP(qubits[i], qubits[n - 1 - i]))

        return circuit

    # Test QFT
    qubits = [cirq.GridQubit(0, i) for i in range(3)]
    qft_circuit = qft_cirq(qubits)

    # Prepare input state |110⟩
    test_circuit = cirq.Circuit()
    test_circuit.append([cirq.X(qubits[0]), cirq.X(qubits[1])])  # |110⟩
    test_circuit += qft_circuit
    test_circuit.append(cirq.measure_each(*qubits))

    simulator = cirq.Simulator()
    result = simulator.run(test_circuit, repetitions=1000)

    qft_counts = {}
    for i in range(1000):
        measurement = ''.join([str(result.measurements[f'{qubit}'][i][0]) for qubit in qubits])
        qft_counts[measurement] = qft_counts.get(measurement, 0) + 1

    print("QFT Results:")
    for outcome, count in sorted(qft_counts.items(), key=lambda x: -x[1]):
        print(f"  |{outcome}⟩: {count:3d} ({count/1000:.3f})")

    # 2. Grover's Algorithm (simplified)
    def grovers_oracle_cirq(qubits, target_state):
        """Create oracle for Grover's algorithm."""
        circuit = cirq.Circuit()

        # Flip qubits that should be 0 in target state
        for i, bit in enumerate(target_state):
            if bit == '0':
                circuit.append(cirq.X(qubits[i]))

        # Multi-controlled Z gate
        if len(qubits) == 2:
            circuit.append(cirq.CZ(qubits[0], qubits[1]))
        elif len(qubits) == 3:
            # Implement CCZ using decomposition
            circuit.append([
                cirq.H(qubits[2]),
                cirq.CCX(qubits[0], qubits[1], qubits[2]),
                cirq.H(qubits[2])
            ])

        # Flip back
        for i, bit in enumerate(target_state):
            if bit == '0':
                circuit.append(cirq.X(qubits[i]))

        return circuit

    def grovers_diffusion_cirq(qubits):
        """Create diffusion operator for Grover's algorithm."""
        circuit = cirq.Circuit()

        # H gates
        circuit.append([cirq.H(qubit) for qubit in qubits])

        # Apply oracle for |000...0⟩ state
        target_zero = '0' * len(qubits)
        oracle = grovers_oracle_cirq(qubits, target_zero)
        circuit += oracle

        # H gates
        circuit.append([cirq.H(qubit) for qubit in qubits])

        return circuit

    # Implement Grover's search for |11⟩ in 2-qubit space
    search_qubits = qubits[:2]
    target = '11'

    grovers_circuit = cirq.Circuit()

    # Initial superposition
    grovers_circuit.append([cirq.H(qubit) for qubit in search_qubits])

    # Optimal number of iterations for 2 qubits: π/4 * √4 ≈ 1
    for _ in range(1):
        # Apply oracle
        oracle = grovers_oracle_cirq(search_qubits, target)
        grovers_circuit += oracle

        # Apply diffusion
        diffusion = grovers_diffusion_cirq(search_qubits)
        grovers_circuit += diffusion

    grovers_circuit.append(cirq.measure_each(*search_qubits))

    grover_result = simulator.run(grovers_circuit, repetitions=1000)

    grover_counts = {}
    for i in range(1000):
        measurement = ''.join([str(grover_result.measurements[f'{qubit}'][i][0]) for qubit in search_qubits])
        grover_counts[measurement] = grover_counts.get(measurement, 0) + 1

    print(f"\nGrover's Search for |{target}⟩:")
    for outcome, count in sorted(grover_counts.items(), key=lambda x: -x[1]):
        print(f"  |{outcome}⟩: {count:3d} ({count/1000:.3f})")

    success_prob = grover_counts.get(target, 0) / 1000
    print(f"Success probability: {success_prob:.3f}")

cirq_quantum_algorithms()

Performance Optimization

Benchmarking and Profiling

def cirq_performance_benchmarking():
    """Benchmark Cirq backend performance."""

    import time
    import cirq

    print("Cirq Performance Benchmarking")
    print("=" * 32)

    # Test different simulators
    simulators = {
        'Simulator': cirq.Simulator(),
        'DensityMatrixSimulator': cirq.DensityMatrixSimulator(),
        # 'SparseSimulator': cirq.SparseSimulator(),  # If available
    }

    # Test different circuit sizes
    circuit_sizes = [2, 4, 6, 8]  # Number of qubits

    results = {}

    for sim_name, simulator in simulators.items():
        print(f"\nTesting {sim_name}:")
        results[sim_name] = {}

        for n_qubits in circuit_sizes:
            # Create test circuit
            qubits = [cirq.GridQubit(0, i) for i in range(n_qubits)]
            circuit = cirq.Circuit()

            # Add parameterized layers
            depth = 3
            for layer in range(depth):
                # Single-qubit layer
                circuit.append([
                    cirq.ry(np.random.random() * 2 * np.pi)(qubit)
                    for qubit in qubits
                ])

                # Two-qubit layer
                for i in range(n_qubits - 1):
                    circuit.append(cirq.CZ(qubits[i], qubits[i + 1]))

            circuit.append(cirq.measure_each(*qubits))

            try:
                # Time execution
                start_time = time.time()
                result = simulator.run(circuit, repetitions=1000)
                execution_time = time.time() - start_time

                # Count unique outcomes
                measurement_counts = {}
                for i in range(1000):
                    measurement = ''.join([
                        str(result.measurements[f'{qubit}'][i][0])
                        for qubit in qubits
                    ])
                    measurement_counts[measurement] = measurement_counts.get(measurement, 0) + 1

                results[sim_name][n_qubits] = {
                    'time': execution_time,
                    'unique_outcomes': len(measurement_counts)
                }

                print(f"  {n_qubits} qubits: {execution_time:.3f}s "
                      f"({len(measurement_counts)} unique outcomes)")

            except Exception as e:
                print(f"  {n_qubits} qubits: Failed - {e}")
                results[sim_name][n_qubits] = {'error': str(e)}

    # Analyze scaling
    print(f"\nScaling Analysis:")
    for sim_name in results:
        print(f"{sim_name}:")
        times = [results[sim_name][n]['time'] 
                for n in circuit_sizes 
                if 'time' in results[sim_name].get(n, {})]

        if len(times) >= 2:
            # Estimate scaling (very rough)
            scaling_factor = times[-1] / times[0] if times[0] > 0 else float('inf')
            theoretical_scaling = 2**(circuit_sizes[-1] - circuit_sizes[0])  # Exponential

            print(f"  Empirical scaling factor: {scaling_factor:.2f}")
            print(f"  Theoretical (exponential): {theoretical_scaling:.2f}")
            print(f"  Scaling efficiency: {theoretical_scaling/scaling_factor:.2f}x")

    # Memory usage estimation
    print(f"\nMemory Usage Estimates:")
    for n_qubits in circuit_sizes:
        state_vector_memory = 2**n_qubits * 16  # Complex128 = 16 bytes
        density_matrix_memory = (2**n_qubits)**2 * 16

        print(f"  {n_qubits} qubits:")
        print(f"    State vector: {state_vector_memory/1024:.1f} KB")
        print(f"    Density matrix: {density_matrix_memory/(1024**2):.1f} MB")

cirq_performance_benchmarking()

Integration Examples

Quantum Machine Learning with Cirq

def cirq_quantum_ml_example():
    """Implement quantum ML using Cirq backend."""

    from sklearn.datasets import make_moons
    from sklearn.model_selection import train_test_split
    from sklearn.preprocessing import StandardScaler
    from sklearn.metrics import accuracy_score
    import cirq

    print("Quantum ML with Cirq Backend")
    print("=" * 30)

    # Generate dataset
    X, y = make_moons(n_samples=100, noise=0.1, random_state=42)
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)

    # Scale features
    scaler = StandardScaler()
    X_train_scaled = scaler.fit_transform(X_train)
    X_test_scaled = scaler.transform(X_test)

    print(f"Dataset: {len(X)} samples, {X.shape[1]} features, 2 classes")

    # Create variational quantum classifier
    class CirqVariationalClassifier:
        def __init__(self, n_qubits=2, n_layers=2):
            self.n_qubits = n_qubits
            self.n_layers = n_layers
            self.qubits = [cirq.GridQubit(0, i) for i in range(n_qubits)]
            self.n_params = n_qubits * n_layers * 2  # RY and RZ per qubit per layer
            self.params = np.random.random(self.n_params) * 2 * np.pi

        def create_ansatz(self, x_data, params):
            """Create variational ansatz circuit."""
            circuit = cirq.Circuit()

            # Data encoding
            for i, xi in enumerate(x_data[:self.n_qubits]):
                circuit.append(cirq.ry(np.pi * xi)(self.qubits[i]))

            # Variational layers
            param_idx = 0
            for layer in range(self.n_layers):
                # Parameterized rotations
                for i in range(self.n_qubits):
                    circuit.append(cirq.ry(params[param_idx])(self.qubits[i]))
                    param_idx += 1
                    circuit.append(cirq.rz(params[param_idx])(self.qubits[i]))
                    param_idx += 1

                # Entangling gates
                if layer < self.n_layers - 1:
                    for i in range(self.n_qubits - 1):
                        circuit.append(cirq.CZ(self.qubits[i], self.qubits[i + 1]))

            return circuit

        def measure_expectation(self, x_data, params):
            """Measure expectation value for classification."""
            circuit = self.create_ansatz(x_data, params)

            # Add measurement
            circuit.append(cirq.measure(self.qubits[0], key='measurement'))

            simulator = cirq.Simulator()
            result = simulator.run(circuit, repetitions=1000)

            # Calculate expectation value of first qubit
            measurements = result.measurements['measurement']
            expectation = np.mean(1 - 2 * measurements)  # Convert {0,1} to {1,-1}

            return expectation

        def predict_single(self, x_data, params=None):
            """Predict single sample."""
            if params is None:
                params = self.params

            expectation = self.measure_expectation(x_data, params)
            return 1 if expectation > 0 else 0

        def cost_function(self, params, X, y):
            """Cost function for training."""
            predictions = []
            for xi in X:
                pred = self.predict_single(xi, params)
                predictions.append(pred)

            # Binary cross-entropy loss
            predictions = np.array(predictions, dtype=float)
            y = np.array(y, dtype=float)

            # Add small epsilon to prevent log(0)
            epsilon = 1e-10
            predictions = np.clip(predictions, epsilon, 1 - epsilon)

            loss = -np.mean(y * np.log(predictions) + (1 - y) * np.log(1 - predictions))
            return loss

        def train(self, X, y, epochs=30, learning_rate=0.1):
            """Train using parameter-shift rule."""
            print(f"Training VQC for {epochs} epochs...")

            cost_history = []

            for epoch in range(epochs):
                # Compute gradients using parameter-shift rule
                gradients = np.zeros_like(self.params)

                for i in range(len(self.params)):
                    # Forward shift
                    params_plus = self.params.copy()
                    params_plus[i] += np.pi / 2
                    cost_plus = self.cost_function(params_plus, X, y)

                    # Backward shift
                    params_minus = self.params.copy()
                    params_minus[i] -= np.pi / 2
                    cost_minus = self.cost_function(params_minus, X, y)

                    # Parameter-shift gradient
                    gradients[i] = (cost_plus - cost_minus) / 2

                # Update parameters
                self.params = self.params - learning_rate * gradients

                # Record cost
                current_cost = self.cost_function(self.params, X, y)
                cost_history.append(current_cost)

                if epoch % 10 == 0:
                    print(f"  Epoch {epoch}: Cost = {current_cost:.4f}")

            return cost_history

        def predict(self, X):
            """Predict multiple samples."""
            predictions = []
            for xi in X:
                pred = self.predict_single(xi)
                predictions.append(pred)
            return np.array(predictions)

    # Create and train classifier
    vqc = CirqVariationalClassifier(n_qubits=2, n_layers=2)
    cost_history = vqc.train(X_train_scaled, y_train, epochs=25, learning_rate=0.3)

    # Make predictions
    train_pred = vqc.predict(X_train_scaled)
    test_pred = vqc.predict(X_test_scaled)

    train_acc = accuracy_score(y_train, train_pred)
    test_acc = accuracy_score(y_test, test_pred)

    print(f"\nResults:")
    print(f"Training Accuracy: {train_acc:.3f}")
    print(f"Test Accuracy: {test_acc:.3f}")

    # Compare with classical ML
    from sklearn.svm import SVC
    classical_svm = SVC(kernel='rbf')
    classical_svm.fit(X_train_scaled, y_train)
    classical_pred = classical_svm.predict(X_test_scaled)
    classical_acc = accuracy_score(y_test, classical_pred)

    print(f"Classical SVM Accuracy: {classical_acc:.3f}")

    # Performance comparison
    if test_acc > classical_acc:
        print("✅ Quantum classifier outperforms classical!")
    elif test_acc > classical_acc - 0.05:
        print("🤔 Quantum classifier is competitive")
    else:
        print("📈 Classical method currently better")

cirq_quantum_ml_example()

Best Practices

Code Organization

# cirq_utils.py
import cirq
import numpy as np

class CirqUtils:
    """Utility functions for Cirq backend operations."""

    @staticmethod
    def create_grid_qubits(rows, cols):
        """Create grid qubit layout."""
        return [cirq.GridQubit(r, c) for r in range(rows) for c in range(cols)]

    @staticmethod
    def create_line_qubits(n):
        """Create line qubit layout."""
        return [cirq.LineQubit(i) for i in range(n)]

    @staticmethod
    def create_connectivity_map(qubits, topology='linear'):
        """Create qubit connectivity map."""
        connections = []

        if topology == 'linear':
            for i in range(len(qubits) - 1):
                connections.append((qubits[i], qubits[i + 1]))

        elif topology == 'circular':
            for i in range(len(qubits)):
                connections.append((qubits[i], qubits[(i + 1) % len(qubits)]))

        elif topology == 'grid':
            # Assumes qubits are GridQubits
            for qubit in qubits:
                if isinstance(qubit, cirq.GridQubit):
                    neighbors = [
                        cirq.GridQubit(qubit.row + dr, qubit.col + dc)
                        for dr, dc in [(0, 1), (1, 0), (0, -1), (-1, 0)]
                    ]
                    for neighbor in neighbors:
                        if neighbor in qubits and (qubit, neighbor) not in connections:
                            connections.append((qubit, neighbor))

        return connections

    @staticmethod
    def optimize_circuit_for_device(circuit, device_connectivity):
        """Optimize circuit for specific device connectivity."""
        # Apply basic optimizations
        optimizers = [
            cirq.MergeSingleQubitGates(),
            cirq.DropEmptyMoments(),
            cirq.DropNegligible()
        ]

        optimized_circuit = circuit.copy()
        for optimizer in optimizers:
            optimizer.optimize_circuit(optimized_circuit)

        return optimized_circuit

Error Handling

def robust_cirq_execution(circuit, simulator_type='Simulator', max_retries=3):
    """Execute Cirq circuit with error handling and fallbacks."""

    simulators = {
        'Simulator': cirq.Simulator,
        'DensityMatrixSimulator': cirq.DensityMatrixSimulator,
        'SparseSimulator': getattr(cirq, 'SparseSimulator', cirq.Simulator)
    }

    for attempt in range(max_retries):
        try:
            simulator_class = simulators.get(simulator_type, cirq.Simulator)
            simulator = simulator_class()

            result = simulator.run(circuit, repetitions=1000)
            return result

        except Exception as e:
            print(f"Attempt {attempt + 1} failed with {simulator_type}: {e}")

            if attempt == max_retries - 1:
                # Final fallback to basic simulator
                if simulator_type != 'Simulator':
                    print("Falling back to basic Simulator...")
                    return robust_cirq_execution(circuit, 'Simulator', 1)
                else:
                    raise e

            # Try simpler simulator on next attempt
            if simulator_type == 'DensityMatrixSimulator':
                simulator_type = 'Simulator'

Resources

Documentation

Hardware Access

  • Google Quantum AI: Apply for access to quantum processors
  • Quantum AI Campus Program: Academic partnerships
  • Cirq Community: Open-source development and support

Cirq's integration with SuperQuantX brings Google's quantum computing expertise to your applications. With its focus on NISQ algorithms and flexible circuit construction, Cirq is ideal for near-term quantum applications and research.

Circuit Optimization

Use Cirq's moment structure and optimization passes to create efficient circuits. The MergeSingleQubitGates optimizer can significantly reduce circuit depth.

Memory Scaling

Density matrix simulation scales as O(4^n) in memory. For circuits with >10 qubits, prefer state vector simulation when possible.

Custom Gates

Cirq makes it easy to create custom gates and operations. This is particularly useful for implementing domain-specific quantum algorithms.