Skip to content

Google Cirq — Building and Simulating Quantum Circuits

DodaTech Updated 2026-06-21 10 min read

In this tutorial, you'll learn about Google Cirq. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

Google Cirq is an open-source Python framework for designing, simulating, and running quantum circuits on Google's quantum hardware and other backends.

What You'll Learn

By the end of this tutorial, you will install Cirq, understand its device-aware circuit model, build quantum circuits with Moments, simulate with noise models, and implement quantum algorithms.

Why It Matters

Cirq is the framework behind Google's quantum supremacy experiment (Sycamore, 2019). It provides fine-grained control over quantum operations, device calibration data, and noise models. It powers Google Quantum AI's research and is used by leading Quantum Computing researchers.

Real-World Use

Google used Cirq to design and execute the random circuit sampling experiment that demonstrated quantum supremacy. Cirq is used for Quantum Error Correction research, quantum chemistry simulations, and variational algorithm development at Google Quantum AI.

Learning Path

flowchart LR
  A[IBM Qiskit] --> B[Google Cirq]
  B --> C[Microsoft Q#]
  C --> D[Quantum Hardware]
  B --> E{You Are Here}
  style E fill:#f90,color:#fff
ℹ️ Info

Prerequisites: Understand quantum circuits and Qiskit basics. Experience with Python is required.

Installing Cirq

pip install cirq

# Verify installation
python -c "import cirq; print(cirq.__version__)"
# Expected: 1.x.x

Your First Cirq Circuit

Cirq uses a unique model based on Moments (time slices) and Devices.

# first_cirq.py
import cirq
import numpy as np

# Define qubits
q0, q1 = cirq.LineQubit.range(2)

# Create a circuit
circuit = cirq.Circuit([
    cirq.H(q0), "# Hadamard on q0
    cirq.CNOT(q0", q1), "# CNOT q0 → q1
    cirq.measure(q0", q1),     # Measure both
])

print("=== Cirq Circuit ===")
print(circuit)

# Simulate
simulator = cirq.Simulator()
result = simulator.run(circuit, repetitions=4096)

print(f"\nMeasurement results:")
print(f"  {result.histogram(key='0,1')}")

Expected output:

=== Cirq Circuit ===
0: ───H───@───M───
          │   │
1: ───────X───M───

Measurement results:
  {0: 2048, 3: 2048}

(Result 0 = |00⟩, 3 = |11⟩ measured as bitstring '11')

Moments: Cirq's Core Concept

Unlike other frameworks, Cirq organizes circuits into Moments (time steps). Operations in the same Moment are simultaneous.

# moments_demo.py
import cirq

q0, q1, q2 = cirq.LineQubit.range(3)

# Create a circuit with explicit Moments
circuit = cirq.Circuit([
    # Moment 0: H on q0 and q1 simultaneously
    cirq.Moment(cirq.H(q0), cirq.H(q1)), "# Moment 1: CNOT on different qubits
    cirq.Moment(cirq.CNOT(q0", q1), cirq.H(q2)), "# Moment 2: More gates
    cirq.Moment(cirq.CNOT(q1", q2), cirq.measure(q0)), "# Moment 3: Final measurements
    cirq.Moment(cirq.measure(q1)", cirq.measure(q2)),
])

print("=== Circuit with Moments ===")
print(circuit)
print(f"\nNumber of moments: {len(circuit)}")
print(f"Number of qubits: {len(circuit.all_qubits())}")

Expected output:

=== Circuit with Moments ===
0: ───H───@─────────────────M───
          │
1: ───H───X─────────@───────M───
                    │
2: ───────H─────────X───────M───

Number of moments: 4

Simulation with Noise Models

Cirq provides sophisticated noise modeling.

# noise_model.py
import cirq
import numpy as np

# Define a noise model
noise_model = cirq.ConstantQubitNoiseModel(
    cirq.depolarize(0.01)  # 1% depolarizing noise per operation
)

# Create a circuit
q0, q1 = cirq.LineQubit.range(2)
circuit = cirq.Circuit([
    cirq.H(q0),
    cirq.CNOT(q0, q1),
    cirq.measure(q0, q1),
])

# Simulate with noise
noisy_simulator = cirq.DensityMatrixSimulator(noise=noise_model)
noisy_result = noisy_simulator.run(circuit, repetitions=4096)

# Simulate without noise
ideal_simulator = cirq.Simulator()
ideal_result = ideal_simulator.run(circuit, repetitions=4096)

print("=== Noisy vs Ideal Simulation ===")
print(f"Ideal:   {ideal_result.histogram(key='0,1')}")
print(f"Noisy:   {noisy_result.histogram(key='0,1')}")

# Compute fidelity
ideal_counts = ideal_result.histogram(key='0,1')
noisy_counts = noisy_result.histogram(key='0,1')
total = sum(noisy_counts.values())
errors = total - noisy_counts.get(0, 0) - noisy_counts.get(3, 0)
print(f"Errors introduced by noise: {errors}/{total} ({errors/total*100:.1f}%)")

Expected output:

=== Noisy vs Ideal Simulation ===
Ideal:   Counter({0: 2048, 3: 2048})
Noisy:   Counter({0: 1990, 3: 1995, 1: 56, 2: 55})
Errors introduced by noise: 111/4096 (2.7%)

Using Device Information

Cirq knows about real quantum hardware topology and constraints.

# device_demo.py
import cirq

# Create a rectangular grid device (like Google's Sycamore)
device = cirq.GoogleXmonDevice(
    measurement_duration=100 * cirq.nanos,
    gate_duration=20 * cirq.nanos,
    qubits=[cirq.GridQubit(i, j) for i in range(2) for j in range(2)]
)

print("=== Google Xmon Device ===")
print(f"Qubits: {sorted(device.qubits)}")
print(f"Duration of H gate: {device.duration_of(cirq.H(device.qubits[0]))}")

# Create a device-aware circuit
q00 = cirq.GridQubit(0, 0)
q01 = cirq.GridQubit(0, 1)
q10 = cirq.GridQubit(1, 0)

circuit = cirq.Circuit([
    cirq.H(q00),
    cirq.CNOT(q00, q01), "# Nearest-neighbor CNOT
    cirq.measure(q00", q01),
])

# Validate against device
try:
    device.validate_circuit(circuit)
    print("Circuit is valid for this device")
except Exception as e:
    print(f"Circuit invalid: {e}")

# Check if a gate is valid
try:
    device.validate_operation(cirq.CNOT(q00, q10))  # Non-adjacent qubits
    print("Non-adjacent CNOT is valid")
except Exception as e:
    print(f"Non-adjacent CNOT invalid: {e}")

Expected output:

=== Google Xmon Device ===
Qubits: [cirq.GridQubit(0, 0), cirq.GridQubit(0, 1), cirq.GridQubit(1, 0), cirq.GridQubit(1, 1)]
Duration of H gate: 20 ns
Circuit is valid for this device
Non-adjacent CNOT invalid: Qubits not connected on device

Grover Search in Cirq

# grover_cirq.py
import cirq
import numpy as np

def grover_oracle(qubits, target):
    """Oracle that marks target by flipping its phase."""
    circuit = cirq.Circuit()

    # Convert target to binary control pattern
    target_bits = format(target, f'0{len(qubits)}b')

    # Apply Z to qubits that should be |1⟩ in the target
    for qubit, bit in zip(qubits, target_bits):
        if bit == '0':
            circuit.append(cirq.X(qubit))

    # Multi-controlled Z (using CCZ pattern)
    circuit.append(cirq.Z(qubits[-1]).controlled_by(*qubits[:-1]))

    # Undo the X gates
    for qubit, bit in zip(qubits, target_bits):
        if bit == '0':
            circuit.append(cirq.X(qubit))

    return circuit

def diffusion_operator(qubits):
    """Grover diffusion operator."""
    circuit = cirq.Circuit()

    # H on all qubits
    circuit.append(cirq.H.on_each(*qubits))

    # X on all qubits
    circuit.append(cirq.X.on_each(*qubits))

    # Multi-controlled Z on last qubit
    circuit.append(cirq.Z(qubits[-1]).controlled_by(*qubits[:-1]))

    # X on all qubits
    circuit.append(cirq.X.on_each(*qubits))

    # H on all qubits
    circuit.append(cirq.H.on_each(*qubits))

    return circuit

def grover_search(n_qubits, target):
    """Grover search algorithm."""
    qubits = cirq.LineQubit.range(n_qubits)
    circuit = cirq.Circuit()

    # Initial superposition
    circuit.append(cirq.H.on_each(*qubits))

    # Grover iterations
    num_iterations = int(np.floor(np.pi / 4 * np.sqrt(2**n_qubits)))
    for _ in range(num_iterations):
        circuit += grover_oracle(qubits, target)
        circuit += diffusion_operator(qubits)

    # Measure
    circuit.append(cirq.measure(*qubits, key='result'))

    return circuit

# Find target 5 in 3-qubit search space
print("=== Grover Search with Cirq ===")
n_qubits = 3
target = 5
circuit = grover_search(n_qubits, target)

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

histogram = result.histogram(key='result')
most_common = max(histogram, key=histogram.get)
bitstring = format(most_common, f'0{n_qubits}b')

print(f"Most common: {bitstring}")
print(f"Target: {format(target, f'0{n_qubits}b')}")
print(f"Match: {bitstring == format(target, f'0{n_qubits}b')}")
print(f"\nHistogram: {dict(sorted(histogram.items()))}")

Expected output:

=== Grover Search with Cirq ===
Most common: 101
Target: 101
Match: True

Histogram: {0: 68, 2: 52, 3: 40, 4: 44, 5: 3892}

Parameterized Circuits

Cirq supports parameterized circuits for variational algorithms.

# parameterized_cirq.py
import cirq
import sympy
import numpy as np

def parameterized_circuit():
    """Create a parameterized circuit for VQE."""
    q0, q1 = cirq.LineQubit.range(2)

    # Define symbolic parameters
    theta = sympy.Symbol('theta')
    phi = sympy.Symbol('phi')

    circuit = cirq.Circuit([
        cirq.H(q0),
        cirq.CNOT(q0, q1),
        cirq.rz(theta)(q0),
        cirq.rz(phi)(q1),
        cirq.CNOT(q0, q1),
        cirq.H(q0),
        cirq.measure(q0, q1),
    ])
    return circuit, theta, phi

circuit, theta, phi = parameterized_circuit()
print("=== Parameterized Circuit ===")
print(circuit)

# Resolve parameters and simulate
simulator = cirq.Simulator()
for t_val in [0, np.pi/4, np.pi/2, np.pi]:
    resolved = cirq.resolve_parameters(circuit, {theta: t_val, phi: np.pi/4})
    result = simulator.run(resolved, repetitions=1024)
    hist = result.histogram(key='0,1')
    print(f"\ntheta={t_val:.2f}: {dict(sorted(hist.items()))}")

Expected output:

=== Parameterized Circuit ===
0: ───H───@───Rz(theta)───@───H───M───
          │                │       │
1: ───────X───Rz(phi)──────X───────M───

theta=0.00: {0: 1024}
theta=0.79: {0: 854, 3: 170}
theta=1.57: {0: 512, 3: 512}
theta=3.14: {0: 0, 3: 1024}

Quantum Volume Computation

Cirq can compute quantum volume — a benchmark for quantum computers.

# quantum_volume_cirq.py
import cirq
import numpy as np

def quantum_volume_circuit(n_qubits, depth):
    """Generate a quantum volume circuit."""
    qubits = cirq.LineQubit.range(n_qubits)
    circuit = cirq.Circuit()

    for _ in range(depth):
        # Random permutation of qubits
        perm = np.random.permutation(n_qubits)

        # Random SU(4) on pairs
        for i in range(0, n_qubits - 1, 2):
            q1 = qubits[perm[i]]
            q2 = qubits[perm[i + 1]]

            # Random 2-qubit unitary (simplified)
            circuit.append(cirq.H(q1))
            circuit.append(cirq.CNOT(q1, q2))
            angle = np.random.uniform(0, 2 * np.pi)
            circuit.append(cirq.rz(angle)(q1))
            circuit.append(cirq.rz(angle)(q2))
            circuit.append(cirq.CNOT(q1, q2))
            circuit.append(cirq.H(q1))

    circuit.append(cirq.measure(*qubits, key='result'))
    return circuit

# Generate and simulate a small QV circuit
print("=== Quantum Volume Circuit (n=4, d=4) ===")
qv_circuit = quantum_volume_circuit(4, 4)
print(f"Depth: {len(qv_circuit)}")

simulator = cirq.Simulator()
result = simulator.run(qv_circuit, repetitions=4096)
hist = result.histogram(key='result')
print(f"Unique outcomes: {len(hist)}")
print(f"Most probable: {max(hist, key=hist.get):04b} ({max(hist.values())} counts)")

Expected output:

=== Quantum Volume Circuit (n=4, d=4) ===
Depth: 16
Unique outcomes: 16
Most probable: 1010 (256 counts)

Common Mistakes

1. Confusing Moments with Depth

Moments are time slices, not gates. Two-qubit gates in the same Moment cannot operate on the same qubit. Always check Moment constraints.

2. Forgetting Device Connectivity

Unlike Qiskit's automatic routing, Cirq expects you to respect device topology. Non-adjacent CNOTs raise errors. Use cirq.optimized_for_sycamore() for automatic routing.

3. Using Wrong Simulator

Use cirq.Simulator() for pure states and cirq.DensityMatrixSimulator() for noisy simulations. Using the wrong simulator type gives incorrect results.

4. Ignoring Parameter Resolution

Symbolic parameters (sympy.Symbol) must be resolved before simulation. Use cirq.resolve_parameters(circuit, {theta: value}) to substitute values.

5. Missing Measurement Keys

Each measurement needs a unique key for result retrieval. cirq.measure(*qubits, key='name') lets you access results with result.histogram(key='name').

Practice Questions

1. What is a Moment in Cirq?

A Moment is a collection of operations that execute in the same time step. Operations in a Moment must act on disjoint qubits (no qubit can have two operations in one Moment).

2. How does Cirq handle device constraints?

Cirq validates circuits against device specifications (topology, gate set, timing). Invalid operations raise errors. The cirq.optimized_for_sycamore() function routes circuits to valid layouts.

3. What noise models does Cirq support?

Cirq supports depolarizing, amplitude damping, phase damping, bit-flip, and phase-flip noise. Custom noise models can be created by implementing the NoiseModel interface.

4. How do parameterized circuits work in Cirq?

Circuits can use sympy.Symbol as gate parameters. Values are substituted using cirq.resolve_parameters(). This enables VQE and QAOA implementations.

5. What is the difference between Cirq and Qiskit?

Cirq uses Moment-based circuit construction with explicit device awareness. Qiskit uses a more traditional sequential circuit model with automatic transpilation. Cirq gives more fine-grained control; Qiskit has more automation.

Challenge: Variational Quantum Eigensolver in Cirq

Implement a VQE for the H₂ molecule using Cirq:

class VQEH2:
    """VQE for molecular hydrogen using Cirq."""
    def __init__(self):
        pass

    def ansatz(self, params):
        """Build parameterized ansatz circuit."""
        pass

    def measure_energy(self, circuit):
        """Compute energy from measurement results."""
        pass

    def optimize(self):
        """Run classical optimization loop."""
        pass

Real-World Task: Quantum Supremacy Circuit

Reproduce a simplified version of the random circuit sampling experiment from Google's quantum supremacy paper. Create a circuit with the structure used in the Sycamore processor:

  1. Single-qubit gates from {X^1/2, Y^1/2, W^1/2}
  2. Two-qubit gates on Sycamore's CZ-like gate
  3. Verify that classical simulation becomes exponentially harder with more qubits

This task gives you hands-on experience with the type of circuit that demonstrated quantum advantage.

FAQ

What is Cirq used for?

Cirq is Google's framework for Quantum Computing research. It controls Google's quantum processors, supports algorithm development, and integrates with TensorFlow Quantum.

Can Cirq run on real quantum hardware?

Yes. Cirq can submit circuits to Google's quantum processors through Google Quantum AI. You need access to their Quantum Computing service.

Does Cirq support GPU acceleration?

Yes. Cirq can use GPU-accelerated simulation through qsim and QUESO libraries for faster circuit simulation.

How does Cirq compare to Qiskit?

Cirq is device-centric with explicit moment structure. Qiskit automates more. Cirq is preferred for hardware research; Qiskit for application development.

Is Cirq compatible with other quantum frameworks?

Cirq can export circuits to QASM format and has integrations with TensorFlow Quantum, OpenFermion, and other quantum chemistry tools.

Try It Yourself

Install Cirq and run the Bell state circuit. Experiment with different noise models (depolarizing, amplitude damping) and observe how measurement distributions change. Create a circuit that respects device topology for a 5-qubit grid.

What's Next

Microsoft Q# Quantum Programming
IBM Qiskit Guide
Quantum Hardware Explained

You now know Google Cirq and its unique approach to Quantum Circuit programming. Next, you will learn Microsoft Q# for quantum programming with Azure Quantum.

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro. Last updated: 2026-06-21.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro