Chapter 14 — Key Takeaways (Google Cirq)

The Cirq page. §14.5 (endianness) is the one that will cost you a day if you skip it.

The same Bell state

import cirq
q = cirq.LineQubit.range(2)
circuit = cirq.Circuit([cirq.H(q[0]), cirq.CNOT(q[0], q[1]),
                        cirq.measure(*q, key="m")])
result = cirq.Simulator(seed=42).run(circuit, repetitions=4096)
result.histogram(key="m")        # {0: 2043, 3: 2053}
Qiskit Cirq
qubits QuantumCircuit(2) — indices cirq.LineQubit.range(2)objects
apply a gate qc.h(0) (mutates) cirq.H(q[0]) (returns an operation)
build append in sequence cirq.Circuit([ops])
measure qc.measure([0,1],[0,1]) cirq.measure(*q, key="m")
run SamplerV2(mode=...).run([isa]) cirq.Simulator().run(c, repetitions=n)
results {'00': 2043} bitstrings {0: 2043} integers
depth qc.depth()computed len(circuit)structural

★ Moments are the central difference

A circuit is a list of Moments. len(circuit) is the depth.

explicit = cirq.Circuit([cirq.Moment([cirq.H(q[0])]),
                         cirq.Moment([cirq.CNOT(q[0], q[1])])])
explicit == cirq.Circuit([cirq.H(q[0]), cirq.CNOT(q[0], q[1])])    # True

Default insertion is a convenience; moment structure is the ground truth.

  three independent H gates      EARLIEST -> 1 moment      NEW -> 3 moments

Same gates, same state (allclose verified), 3× the depth — a constructor argument.

⚛️ A moment is the honest abstraction: what kills your state is wall-clock duration against $T_1$/$T_2$, set by layers, not gate count. Caveat: a moment lasts as long as its slowest member, so depth is a proxy for duration, not a synonym.

InsertStrategy — and the per-call trap

Strategy Behavior
EARLIEST slide left into the earliest moment with room (default)
NEW always start a new moment
INLINE most recent moment if it fits
NEW_THEN_INLINE new moment for the first op, inline the rest
  strategy              one append call    append in a loop
  EARLIEST                            1                   1
  NEW                                 3                   3
  INLINE                              1                   1
  NEW_THEN_INLINE                     1                   3     <- !!

⚠️ The strategy applies PER append CALL, not per operation. A refactor that splits one append into a loop triples the depth without changing a gate. Assert on len(circuit).

★★★ ENDIANNESS — the trap

  X on qubit 0 of a 2-qubit register:
    CIRQ    amplitude at index 2  (binary 10)   -> qubit 0 is the MOST  significant bit
    QISKIT  amplitude at index 1  (binary 01)   -> qubit 0 is the LEAST significant bit
  CIRQ   measure(q0,q1) histogram: {2: 100}    raw row: [1, 0]
  QISKIT counts:                   {'01': 100}

Cirq's 2 and Qiskit's '01' are the same physical outcome.

Why it hides

  correct convention: {0: 2043, 3: 2053}
  WRONG convention:   {0: 2043, 3: 2053}      identical? True

A Bell state is symmetric under bit reversal. So is GHZ. So is any uniform superposition. So is essentially every circuit used to check a fresh install.

🐛 The first circuit you write is the one that cannot catch this. Test with one X gate: text correct: {2: 100} WRONG: {1: 100} identical? False <- NOW it can fail A test whose expected output is invariant under the bug you fear is not a test for that bug.

def reverse_bits(value, n):                 # ONE place, and only one
    return int(format(value, f"0{n}b")[::-1], 2)

Bit reversal is its own inverse, so scattered conversions cancel in pairs and make the bug intermittent. Convert once, at the boundary.

simulate() vs run()

  simulate() -> the STATE   [0.7071, 0, 0, 0.7071]      (no measurement needed)
  run()      -> SAMPLES     {0: 503, 3: 497}            (measurement REQUIRED)
  run() on an unmeasured circuit: ValueError: Circuit has no measurements to sample.

simulate() exposes result.qubit_mapthe authoritative ordering when in doubt.

Gatesets instead of transpilation

cirq.optimize_for_target_gateset(circuit, gateset=cirq.CZTargetGateset())
  ['H', 'CNOT']  ->  ['PhXZ(a=0.5,x=0.5,z=0)', 'PhXZ(...)', 'CZ', 'PhXZ(...)', 'PhXZ(...)']
  moments 2 -> 3      unitary preserved (allclose verified)

PhXZ is Cirq's three-parameter $SU(2)$ gate — the role Qiskit's rz/sx pair plays.

⚙️ No optimization_level, no unified layout+routing stage, no seed_transpiler. Routing exists (cirq.RouteCQC) as a separate tool. Qiskit's compilation stack is substantially more developed — it solves a different problem: arbitrary circuits onto heterogeneous devices behind a queue.

Sweeps — sympy, bound BY NAME

t = sympy.Symbol("t")
sweep = cirq.Linspace(t, start=0, stop=np.pi, length=5)
results = cirq.Simulator(seed=42).run_sweep(circuit, params=sweep, repetitions=1000)
  t=0.0000  P(1)=0.000 (sin²(t/2)=0.000)    t=1.5708  P(1)=0.502 (0.500)
  t=0.7854  P(1)=0.149 (0.146)              t=2.3562  P(1)=0.859 (0.854)

★ Chapter 8's worst bug cannot happen here. theta1…theta12 sorts lexicographically as theta1, theta10, theta11, theta12, theta2…, and Qiskit's positional list binding puts 11 of 12 values in the wrong gate. cirq.ParamResolver binds by name, through a dict. There is no order to get wrong.

Symbolic expressions come free: cirq.ry(2*t + sympy.pi/4) needs no special API.

Noise and devices

noisy = circuit.with_noise(cirq.depolarize(p=0.05))   # moments 2->4, ops 2->6

Error fraction 0.0930 — same order as Chapter 11's Aer measurement. Channels map onto Aer's (depolarize, amplitude_damp, phase_damp, bit_flip, phase_flip). Phase damping is invisible in the computational basis here too (γ = 0.0/0.3/0.6 → indistinguishable histograms) — Chapter 11 §11.7 confirmed in a second framework.

Qubit type Use
LineQubit 1-D chain
GridQubit 2-D lattice — Google's architecture; GridQubit(3,4).is_adjacent(GridQubit(3,5))True
NamedQubit arbitrary labels

cirq_google.Sycamore: GridDevice, 54 qubits, 88 coupling pairs. Carries topology, not calibration — no per-qubit readout error or $T_1$/$T_2$. No Cirq equivalent of NoiseModel.from_backend or the fake-provider fleet.

🔬 Hardware access is the real asymmetry

IBM offers open access — token, run today. That is the basis of Chapter 2 and every hardware result in this book. Google's service has historically run through research partnerships; check current terms. Learn and simulate in Cirq; expect to touch hardware through Qiskit. A statement about access policy, not quality.

🗝️ Windows papercut

  UnicodeEncodeError: 'charmap' codec can't encode characters in position 3-5

Cirq diagrams use box-drawing characters. Fix: sys.stdout.reconfigure(encoding="utf-8") or PYTHONIOENCODING=utf-8. Verified with cirq 1.7.0 on Windows 10.

Choosing

Task Reach for
IBM hardware today · device-accurate noise · full compilation pipeline Qiskit
Explicit timing · grid topologies · symbolic sweeps · circuits as data Cirq
Variational/ML with autodiff PennyLane (Ch. 16)
Multi-vendor from one API Braket (Ch. 17)
Type system + resource estimation Q# (Ch. 15)

Not competitors — different bets about what is hard. Qiskit bets on compilation and hardware heterogeneity; Cirq bets on precise control.

Common pitfalls

  • Verifying a port with Bell/GHZ states only.
  • Scattering [::-1] instead of converting once.
  • Assuming optimization_level exists.
  • Refactoring an append into a loop under NEW_THEN_INLINE.
  • Expecting run() to work without a measurement.
  • Expecting Cirq device objects to carry calibration data.

Project piece added this chapter

vqelab/translate.pyreverse_bits() (the only function permitted to touch bit order), histogram/state-vector converters, qiskit_to_cirq() (raises on unsupported gates rather than approximating), assert_same_state(). 12 tests pass, including test_x_on_qubit_zero_lands_at_different_indices and — deliberately — test_bell_state_cannot_detect_a_reversed_convention, which asserts the weakness of the obvious test so nobody deletes the asymmetric ones as redundant.