Part V. Practical Software · Chapter 39
Cirq and Alternative Toolchains
Every quantum SDK can run a Bell circuit. They differ in what they make easy, what they hide, and how much maintenance they will cost you over two years. This chapter turns SDK choice from a taste question into an evidence question.
In this chapter 10 sections
Compare one fixed circuit contract across tools using explicit qubit order, native result objects, simulator mode, compilation target, versions, and identical statistical tests; choose a tool from workload fit and maintenance cost, not feature counts or popularity.
Portability begins with a test oracle that belongs to neither SDK. The comparison then measures the translation tax instead of mistaking two different APIs for two different algorithms.
Freeze the circuit contract before choosing syntax
The fixture declares two qubits, q0 as the most-significant displayed bit, H(q0), CNOT(q0,q1), computational-basis measurement, exact support {00,11}, probabilities 0.5/0.5, seed, shots, and an acceptance rule. It also declares simulator mode: exact state-vector comparison and sampled comparison are separate.
Write this as plain data and assertions. If the contract is embedded in one adapter’s result types, the second adapter will be judged against the first SDK rather than against the circuit. OpenQASM 3 may supply an interchange representation, but serialized equivalence does not erase backend-specific compilation or execution [OpenQASM 3 specification].
Cirq's qubits, moments, and simulator result
Cirq asks the programmer to choose explicit qubit objects, organizes scheduled operations into moments, and returns native simulator and measurement result objects [Cirq documentation]. Preserve those concepts in the adapter. Record ordered qubits, moment structure, simulator type, resolver options, measurement key, repetitions, seed, version, and dependency lock hash.
Normalization happens after inspection. Convert the native measurement array to book-visible strings using a tested qubit-column map. Store both that map and the native shape. A two-qubit Bell histogram can look correct after an accidental reversal because 00 and 11 are palindromes; include asymmetric basis fixtures 01 and 10 so ordering errors cannot hide.
Consider the native row [0,1] returned for ordered qubits (q1,q0). It denotes q1=0 and q0=1, so the book’s q0-most-significant string is 10, not 01. The repair is a column permutation derived from qubit identities. Reversing every string happens to repair this two-qubit case, but it is not a general adapter: measurement keys may cover subsets of qubits, appear in a different order, or be concatenated from multiple registers.
Moments also carry semantics that a flat operation list loses. Operations in one moment are intended to coexist under Cirq’s scheduling model; a second tool may express the same partial order through dependencies or barriers. Preserve the native schedule and compare only a declared derived quantity, such as critical-path layers after both adapters apply the same dependency rule.
A second adapter under the same assertions
Qiskit’s execution abstraction and result conventions warrant a separate adapter [Qiskit documentation]. The second adapter may use different qubit identifiers, scheduling concepts, and transpilation hooks. It must still emit a normalized record with circuit ID, displayed-bit convention, exact or sampled mode, support/probability or counts, shots, seed, SDK version, and native-record hash.
The shared harness first tests deterministic basis circuits, then the exact Bell state, then seeded samples. It refuses unknown result fields rather than guessing. An adapter can be unavailable without making the core contract fail, but the comparison record must say unavailable and why.
The failure sequence matters. The 01 and 10 cases diagnose bit and qubit order before entanglement is involved. A phase fixture such as H–Z–H on one qubit diagnoses whether a simulator preserved relative phase. Only then does the Bell case exercise the two-qubit operation and sampling path. When a port fails, this ladder identifies the first semantic layer that diverged instead of producing one unhelpful “histograms differ” message.
Cross-SDK Bell conformance harness
| field | Cirq source | second-tool source | shared meaning |
|---|---|---|---|
| bit_order | ordered qubits/columns | classical map | q0-most-significant |
| support | state or measurement array | quasi/count result | display strings with nonzero mass |
| schedule | moments | transpiled depth | kept native; not equated |
| provenance | version + lock | version + lock | required independently |
contract = {"order": ("q0", "q1"), "support": {"00", "11"}, "p": {"00": .5, "11": .5}}
def canonical_counts(record):
columns = record.get("columns")
counts = record.get("counts", {})
if (type(columns) is not tuple or len(columns) != len(set(columns)) or
set(columns) != set(contract["order"]) or not counts):
raise ValueError("adapter columns must be a permutation of the contract order")
output = {}
for bitstring, count in counts.items():
if len(bitstring) != len(columns) or type(count) is not int or count < 0:
raise ValueError("counts must use fixed-width bit strings and integer counts")
by_qubit = dict(zip(columns, bitstring))
canonical = "".join(by_qubit[qubit] for qubit in contract["order"])
output[canonical] = output.get(canonical, 0) + count
return output
cirq_bell = canonical_counts({"columns": ("q0", "q1"), "counts": {"00": 501, "11": 499}})
second_bell = canonical_counts({"columns": ("q1", "q0"), "counts": {"00": 501, "11": 499}})
cirq_asymmetric = canonical_counts({"columns": ("q0", "q1"), "counts": {"01": 700, "10": 300}})
second_asymmetric = canonical_counts({"columns": ("q1", "q0"), "counts": {"10": 700, "01": 300}})
wrong_declaration = canonical_counts({"columns": ("q0", "q1"), "counts": {"10": 700, "01": 300}})
invalid_rejected = False
try:
canonical_counts({"columns": ("q0", "q0"), "counts": {"00": 1}})
except ValueError:
invalid_rejected = True
assert set(cirq_bell) == contract["support"] and cirq_bell == second_bell
assert cirq_asymmetric == second_asymmetric == {"01": 700, "10": 300}
assert wrong_declaration != cirq_asymmetric and invalid_rejected
print(f"PASS: 39 adapter Bell={cirq_bell} asymmetric={cirq_asymmetric} wrong_order={wrong_declaration}")
Differences the equality test cannot erase
Equal ideal probabilities say nothing about compilation target, native gates, scheduling, cloud access, parameter binding, noise model, dynamic-circuit support, or maintenance surface. Measure code and dependency size in files or bytes; compile and run time in seconds; shots as counts. Report serialized size and whether round-trip parsing preserves the contract. Do not convert moments into depth unless the scheduling definitions truly match.
Artifact comparison needs runnable evidence and declared environment boundaries, not feature-matrix checkmarks [ACM artifact review guidance]. The most expensive difference may be organizational: which SDK the target hardware supports, how often APIs migrate, and who owns the adapter when they do.
Noise simulators are particularly easy to compare badly. A channel attached “after each gate” depends on how each compiler decomposes the circuit; equal channel parameters do not imply equal noisy processes when native gate counts differ. Either compare an agreed compiled operation trace or label each noisy result as tool-specific. Likewise, a state-vector simulator and a tensor-network simulator may agree on a small fixture while having radically different tractable circuit families.
A workload-specific selection record
A teaching simulator values readability, deterministic local execution, and a small dependency boundary. A hardware characterization project values target integration, calibration access, pulse or schedule controls, and result metadata. State the workload, weighted requirements, measured adapter costs, chosen version, and reversal conditions. “More popular” is not a requirement; “maintained by the target team and exposes required calibration fields” can be.
Make reversal conditions measurable. A choice can be revisited if the pinned environment stops receiving security fixes, the target backend drops the integration, median compilation time exceeds the project’s CI budget, or a required dynamic-circuit feature becomes available only through the other adapter. Recording the owner and next review date turns tool choice from folklore into a maintainable engineering decision.
The GHZ extension strengthens, but does not replace, asymmetric fixtures. Exact support {000,111} tests a three-qubit entangling chain and normalized qubit order, while prepared 001 and 100 still expose a full reversal that GHZ cannot. The conformance report therefore names which defect each case can reveal. Test portfolios should be designed for identifiability, not chosen because their output looks recognizably quantum.
Portability is earned at a boundary. The project promises that each pinned adapter maps the declared circuit contract into the common semantic record; it does not promise identical native schedules, noise attachments, intermediate representations, or future APIs. That narrower promise is testable and useful. A broad claim that “the code runs everywhere” is neither.
Retain native exceptions too. Normalizing every failure to “adapter unavailable” would erase whether construction, simulation, measurement extraction, or bit-order translation failed.
GHZ adapter extension
Prompt: Add a GHZ circuit to both adapters and make the shared harness test exact ideal support plus seeded sampled support.
Deliverable: Two implementations, normalized records, shared tests, and a one-page choice memo for a named research or teaching workload.
Pass condition: Both implementations satisfy the same semantic oracle, all convention translations are explicit, and the choice memo cites measured maintenance or compilation differences.
Reference comparison
Format: Reference GHZ adapters, expected normalized result records, and selection-memo rubric.
Verification: Run both pinned test environments and compare normalized exact probabilities within tolerance and count support against the shared oracle.
The normalized GHZ records have exact support on 000 and 111 with probability one half each, while each native record retains its own column ordering and schedule metadata. The selection memo chooses a tool only for the named workload and lists the integration or maintenance measurement that would reverse that choice.
Companion work
Artifacts for this chapter
These entries resolve to checked-in local source. Commands are reproduced exactly from the chapter manifest, and source-embedded fixtures are exported as direct downloads.
Reproduce or test
python3 tools/validate_briefs.py --briefs data/editorial_briefs_36_63.json --from 36 --through 63 --check-rewritten-sources --execute-artifacts
Provenance
Sources and review
- Google Quantum AI. Cirq documentation. Google. 2026official documentation
- IBM Quantum. Qiskit documentation. IBM. 2026official documentation
- OpenQASM Technical Steering Committee. OpenQASM 3 specification. Linux Foundation Joint Development Foundation. 2026official technical specification
- Association for Computing Machinery. Artifact Review and Badging. ACM Publications. 2026official reproducibility policy
The load-bearing claims in the chapter are mapped inline to this registered source set. A citation supports only the bounded claim beside it.