Google Cirq — Building and Simulating Quantum Circuits
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
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:
- Single-qubit gates from {X^1/2, Y^1/2, W^1/2}
- Two-qubit gates on Sycamore's CZ-like gate
- 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
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
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