Google Cirq Guide — Quantum Programming Framework Explained
In this tutorial, you'll learn about Google Cirq Guide. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
Google Cirq is an open-source Quantum Computing framework that provides fine-grained control over Quantum Circuit construction, scheduling, and execution on Google quantum processors using Python.
What You'll Learn
By the end of this tutorial, you will understand Cirq's moment-based circuit model, how to build and optimize circuits, use Cirq simulators, define custom gates and noise models, apply device-specific compilation for Google hardware, and integrate with external frameworks.
Why It Matters
Cirq powers Google's Quantum Computing stack, including the Sycamore processor that demonstrated quantum supremacy. Its unique moment-based scheduling gives developers explicit control over timing and parallelism, which is critical for achieving optimal performance on real hardware.
Real-World Use
Google's 2019 quantum supremacy experiment used Cirq to design and optimize random circuit sampling on the 53-qubit Sycamore processor. Cirq is used in Google's TensorFlow Quantum for hybrid quantum-classical Machine Learning, and by researchers at universities worldwide.
Learning Path
flowchart LR
A[IBM Qiskit] --> B[Google Cirq]
C[Quantum Gates] --> B
B --> D[Quantum Algorithms]
B --> E{You Are Here}
style E fill:#f90,color:#fff
Prerequisites: Python 3.8+ installed. Understand quantum gates and circuits. Familiarity with Qiskit helps for comparison.
Installation and Basics
# Install Cirq (run in terminal)
# pip install cirq
# cirq_basics.py
import cirq
import numpy as np
print("=== Cirq Basics ===")
# Define qubits
q0, q1 = cirq.LineQubit.range(2)
# Build a circuit (moment-based)
circuit = cirq.Circuit([
cirq.H(q0),
cirq.CNOT(q0, q1),
cirq.measure(q0, q1, key='result')
])
print("Circuit:")
print(circuit)
# Simulate
simulator = cirq.Simulator()
result = simulator.run(circuit, repetitions=1024)
counts = result.histogram(key='result')
print(f"\nResults: {counts}")
# Convert counts to bitstrings
bitstring_counts = {}
for outcome, count in counts.items():
bits = format(outcome, f'0{2}b')
bitstring_counts[bits] = count
print(f"Bitstring counts: {bitstring_counts}")
# Cirq version
print(f"\nCirq version: {cirq.__version__}")
Expected output:
=== Cirq Basics ===
Circuit:
0: ───H───@───M('result')───
│
1: ───────X───M─────────────
Results: {0: 524, 3: 500}
Bitstring counts: {'00': 524, '11': 500}
Moment-Based Circuit Model
Unlike Qiskit's instruction-by-instruction model, Cirq organizes gates into moments (time steps).
# cirq_moments.py
import cirq
import numpy as np
print("=== Moments in Cirq ===")
# Define qubits on a grid
qubits = cirq.GridQubit.rect(2, 3) # 2x3 grid
print(f"Qubits: {qubits}")
# Build circuit with explicit moments
circuit = cirq.Circuit()
# Moment 0: Hadamard on all qubits
moment0 = cirq.Moment(cirq.H(q) for q in qubits)
circuit.append(moment0)
# Moment 1: CNOT pairs
moment1 = cirq.Moment([
cirq.CNOT(qubits[0], qubits[1]),
cirq.CNOT(qubits[2], qubits[3]),
cirq.CNOT(qubits[4], qubits[5]),
])
circuit.append(moment1)
# Moment 2: Measure all
moment2 = cirq.Moment(cirq.measure(q, key=f'm_{i}') for i, q in enumerate(qubits))
circuit.append(moment2)
print("Circuit with moments:")
print(circuit)
print(f"\nNumber of moments: {len(circuit)}")
# Operations per moment
for i, moment in enumerate(circuit):
print(f" Moment {i}: {len(moment)} operations")
# Insert operations at specific moments
q = cirq.LineQubit(0)
custom = cirq.Circuit([
cirq.Moment([cirq.H(q)]),
cirq.Moment([cirq.T(q)]),
cirq.Moment([cirq.H(q)]),
])
print(f"\nCustom timing circuit:\n{custom}")
# Optimize: merge consecutive single-qubit gates
from cirq.transformers import MergeSingleQubitGates
optimized = MergeSingleQubitGates()(custom)
print(f"\nAfter merging:\n{optimized}")
Expected output:
=== Moments in Cirq ===
Qubits: [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2)]
Simulators
Cirq provides multiple simulators for different use cases.
# cirq_simulators.py
import cirq
import numpy as np
# Define qubits
q = cirq.LineQubit.range(3)
# Create a GHZ circuit
ghz = cirq.Circuit([
cirq.H(q[0]),
cirq.CNOT(q[0], q[1]),
cirq.CNOT(q[0], q[2]),
cirq.measure(*q, key='result')
])
# 1. Exact statevector simulator
print("=== Statevector Simulator ===")
sv_sim = cirq.Simulator()
result = sv_sim.simulate(ghz)
print(f"Final state: {result.final_state_vector}")
print(f"Bloch vector q0: {cirq.bloch_vector_from_state_vector(result.final_state_vector, 0)}")
# 2. Probabilistic simulator
print("\n=== Probabilistic Simulator ===")
result = sv_sim.run(ghz, repetitions=4096)
counts = result.histogram(key='result')
for outcome, count in sorted(counts.items()):
bits = format(outcome, '03b')
print(f" |{bits}⟩: {count/4096*100:.1f}%")
# 3. Density matrix simulator (for noisy simulations)
print("\n=== Density Matrix Simulator ===")
dm_sim = cirq.DensityMatrixSimulator()
result_dm = dm_sim.simulate(ghz[:-1]) # Without measurement
print(f"Density matrix shape: {result_dm.final_density_matrix.shape}")
print(f"Purity: {np.trace(result_dm.final_density_matrix @ result_dm.final_density_matrix).real:.4f}")
# 4. Clifford simulator (stabilizer)
print("\n=== Clifford Simulator ===")
# Build a Clifford-only circuit
q_cliff = cirq.LineQubit.range(3)
clifford_circuit = cirq.Circuit([
cirq.H(q_cliff[0]),
cirq.S(q_cliff[1]),
cirq.CNOT(q_cliff[0], q_cliff[1]),
cirq.CNOT(q_cliff[1], q_cliff[2]),
cirq.measure(*q_cliff, key='result')
])
cliff_sim = cirq.CliffordSimulator()
result_cliff = cliff_sim.run(clifford_circuit, repetitions=1024)
print(f"Clifford circuit results: {dict(sorted(result_cliff.histogram(key='result').items()))}")
Expected output:
=== Statevector Simulator ===
Final state: [0.70710678+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.70710678+0.j]
Noise Models
Cirq provides comprehensive noise modeling for realistic simulations.
# cirq_noise.py
import cirq
import numpy as np
print("=== Noise Models in Cirq ===")
q = cirq.LineQubit.range(2)
circuit = cirq.Circuit([
cirq.H(q[0]),
cirq.CNOT(q[0], q[1]),
cirq.measure(*q, key='result')
])
# 1. Depolarizing noise
print("\n1. Depolarizing Noise (p=0.05)")
noise_depol = cirq.ConstantQubitNoiseModel(
cirq.depolarize(0.05)
)
result_depol = cirq.DensityMatrixSimulator(noise=noise_depol).run(
circuit, repetitions=4096
)
counts_depol = result_depol.histogram(key='result')
for outcome, count in sorted(counts_depol.items()):
bits = format(outcome, '02b')
print(f" |{bits}⟩: {count/4096*100:.1f}%")
# 2. Amplitude damping
print("\n2. Amplitude Damping (gamma=0.1)")
noise_amp = cirq.ConstantQubitNoiseModel(
cirq.amplitude_damp(0.1)
)
result_amp = cirq.DensityMatrixSimulator(noise=noise_amp).run(
circuit, repetitions=4096
)
counts_amp = result_amp.histogram(key='result')
for outcome, count in sorted(counts_amp.items()):
bits = format(outcome, '02b')
print(f" |{bits}⟩: {count/4096*100:.1f}%")
# 3. Custom noise channel
print("\n3. Custom Bit-Flip Noise")
noise_custom = cirq.ConstantQubitNoiseModel(
cirq.bit_flip(0.1)
)
result_custom = cirq.DensityMatrixSimulator(noise=noise_custom).run(
circuit, repetitions=4096
)
counts_custom = result_custom.histogram(key='result')
for outcome, count in sorted(counts_custom.items()):
bits = format(outcome, '02b')
print(f" |{bits}⟩: {count/4096*100:.1f}%")
# 4. Compare fidelity
def compute_fidelity(ideal_counts, noisy_counts):
ideal_dist = {k: v/sum(ideal_counts.values()) for k, v in ideal_counts.items()}
noisy_dist = {k: v/sum(noisy_counts.values()) for k, v in noisy_counts.items()}
fidelity = sum(np.sqrt(ideal_dist.get(k, 0) * noisy_dist.get(k, 0)) for k in ideal_dist)
return fidelity
# Ideal result
ideal_result = cirq.Simulator().run(circuit, repetitions=4096)
ideal_counts = ideal_result.histogram(key='result')
print(f"\n=== Fidelity Comparison ===")
print(f"Ideal vs Depolarizing: {compute_fidelity(ideal_counts, counts_depol):.4f}")
print(f"Ideal vs Amplitude Damping: {compute_fidelity(ideal_counts, counts_amp):.4f}")
Expected output:
=== Noise Models in Cirq ===
1. Depolarizing Noise (p=0.05)
|00⟩: 47.5%
|11⟩: 45.3%
|01⟩: 3.6%
|10⟩: 3.6%
Device-Specific Compilation
Cirq compiles circuits for specific quantum processors.
# cirq_device.py
import cirq
import numpy as np
print("=== Device-Specific Compilation ===")
# Define a circuit
q = cirq.LineQubit.range(4)
circuit = cirq.Circuit([
cirq.H(q[0]),
cirq.H(q[1]),
cirq.CNOT(q[0], q[2]),
cirq.CNOT(q[1], q[3]),
cirq.rz(0.25)(q[0]),
cirq.rz(0.5)(q[2]),
cirq.measure(*q, key='result')
])
print("Original circuit:")
print(circuit)
print(f"Depth: {len(circuit)}")
# 1. Validate against device
# Create a simulated device (e.g., Sycamore-like)
class SimulatedSycamore:
"""Simulate Sycamore-style device constraints."""
def __init__(self):
self.qubits = cirq.GridQubit.rect(2, 2)
self.native_gates = {cirq.H, cirq.CNOT, cirq.X, cirq.Z,
cirq.rz, cirq.PhasedXPowGate}
def validate(self, circuit):
"""Check if circuit complies with device."""
errors = []
for moment in circuit:
for op in moments:
if not isinstance(op, cirq.GateOperation):
continue
gate_type = type(op.gate)
if gate_type not in self.native_gates:
errors.append(f"Gate {gate_type} not native")
return errors
device = SimulatedSycamore()
print(f"\nDevice qubits: {device.qubits}")
# 2. Convert to grid qubits
grid_qubits = cirq.GridQubit.rect(2, 2)
# Map LineQubit to GridQubit (simplified mapping)
qubit_map = {q[i]: grid_qubits[i] for i in range(min(4, len(grid_qubits)))}
# 3. Optimize for device
from cirq.transformers import (
optimize_for_target_gateset,
DropNegligible,
)
# Define Sycamore gateset
sycamore_gateset = cirq.Gateset(
cirq.H,
cirq.CNOT,
cirq.X,
cirq.Z,
cirq.rz,
cirq.PhasedXPowGate,
)
optimized = cirq.optimize_for_target_gateset(
circuit,
gateset=sycamore_gateset
)
print(f"\nOptimized for Sycamore:")
print(f"Depth: {len(optimized)}")
print(optimized)
# 4. Compile to native operations
print(f"\n=== Compilation Summary ===")
print(f"Original depth: {len(circuit)}")
print(f"Optimized depth: {len(optimized)}")
print(f"Original operations: {sum(len(m) for m in circuit)}")
print(f"Optimized operations: {sum(len(m) for m in optimized)}")
Expected output:
=== Device-Specific Compilation ===
Original circuit:
(0, 0): ───H───@───Rz(0.25)───M('result')───
│
(0, 1): ───H───┼──────────────M─────────────
│
(0, 2): ───────X───Rz(0.5)────M─────────────
Custom Gates and Operations
Cirq makes it easy to define custom gates.
# cirq_custom_gates.py
import cirq
import numpy as np
print("=== Custom Gates in Cirq ===")
# 1. Define a custom gate as a class
class CustomRotation(cirq.Gate):
"""A custom rotation gate around an arbitrary axis."""
def __init__(self, theta, phi):
super().__init__()
self.theta = theta
self.phi = phi
def _num_qubits_(self):
return 1
def _unitary_(self):
cos = np.cos(self.theta/2)
sin = np.sin(self.theta/2)
return np.array([
[cos, -1j * np.exp(-1j * self.phi) * sin],
[-1j * np.exp(1j * self.phi) * sin, cos]
], dtype=complex)
def _circuit_diagram_info_(self, args):
return f"R({self.theta:.2f}, {self.phi:.2f})"
def __repr__(self):
return f"CustomRotation({self.theta}, {self.phi})"
# Test custom gate
q = cirq.LineQubit(0)
custom_gate = CustomRotation(np.pi/3, np.pi/4)
circuit = cirq.Circuit([
custom_gate(q),
cirq.measure(q, key='result')
])
print(f"Custom gate circuit:\n{circuit}")
# Verify unitarity
U = cirq.unitary(custom_gate)
print(f"\nUnitary:\n{U}")
print(f"Is unitary: {np.allclose(U @ U.conj().T, np.eye(2))}")
# 2. Define a custom operation with classical control
class ConditionalReset(cirq.Operation):
"""Reset qubit if measurement result is 1."""
def __init__(self, qubit, meas_key):
super().__init__()
self._qubit = qubit
self._meas_key = meas_key
@property
def qubits(self):
return (self._qubit,)
def with_qubits(self, *new_qubits):
return ConditionalReset(new_qubits[0], self._meas_key)
def _decompose_(self, qubits):
q, = qubits
# If measured 1, apply X to flip back to 0
yield cirq.X(q).with_classical_controls(self._meas_key)
# 3. Gate with custom decomposition
class ToffoliWithCustomDecomp(cirq.Gate):
"""Toffoli gate decomposed into H, T, CNOT."""
def _num_qubits_(self):
return 3
def _decompose_(self, qubits):
q0, q1, q2 = qubits
# Standard Toffoli decomposition
yield cirq.H(q2)
yield cirq.CNOT(q1, q2)
yield cirq.T(q2).with_inverse()
yield cirq.CNOT(q0, q2)
yield cirq.T(q2)
yield cirq.CNOT(q1, q2)
yield cirq.T(q2).with_inverse()
yield cirq.CNOT(q0, q2)
yield cirq.T(q1)
yield cirq.T(q2)
yield cirq.CNOT(q0, q1)
yield cirq.H(q2)
yield cirq.T(q1).with_inverse()
yield cirq.CNOT(q0, q1)
yield cirq.T(q0)
yield cirq.T(q1)
q = cirq.LineQubit.range(3)
toff = ToffoliWithCustomDecomp().on(*q)
circuit = cirq.Circuit(toff)
print(f"\nToffoli decomposition:\n{circuit}")
print(f"Toffoli decomposition depth: {len(circuit)}")
Expected output:
=== Custom Gates in Cirq ===
Custom gate circuit:
0: ───R(1.05, 0.79)───M('result')───
Common Mistakes
1. Confusing Moments with Instructions
A Cirq Moment contains all operations that execute in parallel. Operations in the same Moment must act on disjoint qubits. Placing two gates on the same qubit in one Moment causes an error.
2. Forgetting to Specify a Qubit Naming Convention
Cirq supports multiple qubit types (LineQubit, GridQubit, NamedQubit). Mixing types in the same circuit works but can be confusing. Choose one convention and stick with it.
3. Using Simulator for Measured Circuits Expecting Statevector
Once you measure, the simulator collapses the state. Use simulate (before measurement) for the statevector and run (after measurement) for measurement samples.
4. Not Handling Classical Control Properly
Cirq supports classical feedforward via with_classical_controls(). Mid-circuit measurements can control later gates, but this requires proper key management.
5. Ignoring Device Topology
Google hardware uses 2D grid topologies. A circuit that works on simulators may fail validation on real devices due to connectivity constraints. Always validate and compile for the target device.
Practice Questions
1. What is a Moment in Cirq and why is it important?
A Moment is a collection of gates that execute in parallel on disjoint qubits. Moments give explicit control over circuit timing and parallelism, enabling precise scheduling for hardware execution.
2. How does Cirq differ from Qiskit in circuit construction?
Cirq uses moment-based construction where operations are grouped into time steps. Qiskit uses instruction-based construction where operations are appended sequentially. Cirq gives finer timing control.
3. What noise models does Cirq support?
Cirq supports depolarizing, amplitude damping, phase damping, bit-flip, phase-flip, and custom noise channels via the ConstantQubitNoiseModel and NoiseModel classes.
4. How do you define a custom gate in Cirq?
Subclass cirq.Gate, implement _num_qubits_ and _unitary_ methods, and optionally _circuit_diagram_info_ and _decompose_ for display and optimization.
Challenge: Quantum Fourier Transform in Cirq
Implement the QFT circuit in Cirq for n qubits. Use controlled phase gates and SWAP gates:
def quantum_fourier_transform(qubits):
"""
Implement QFT on the given qubits using Cirq.
Args:
qubits: list of cirq.Qubit
Returns:
cirq.Circuit implementing QFT
"""
pass
Hints: Use cirq.H, cirq.CZPowGate for controlled phase, and cirq.SWAP for reversing qubit order.
Real-World Task: Random Circuit Sampling
Implement a random Quantum Circuit sampler similar to Google's supremacy experiment:
- Generate a random circuit with alternating single-qubit and two-qubit gates on a 2D grid
- Simulate the output distribution using Cirq's Clifford simulator (for small instances)
- Compare the distribution to a uniform distribution using cross-entropy benchmarking
This was the exact protocol used in Google's landmark 2019 quantum supremacy demonstration on the 53-qubit Sycamore processor.
FAQ
What's Next
You now understand how to program quantum computers with Google Cirq. Next, you will explore Microsoft's Q# language.
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro. Last updated: 2026-06-23.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro