Appendix E: Framework Translation Dictionary
Side-by-side equivalents for Qiskit, Cirq, PennyLane, Q#, and Braket. Chapter 18 measured what gets lost in translation; this is the working reference.
⚠️ Read the endianness section first. It is the single most common source of silently wrong results when moving circuits between frameworks.
Endianness — read this first
| Framework | Convention | |01⟩ means |
|---|---|---|
| Qiskit | little-endian | qubit 0 = 1, qubit 1 = 0 |
| Cirq | big-endian | qubit 0 = 0, qubit 1 = 1 |
| PennyLane | big-endian | qubit 0 = 0, qubit 1 = 1 |
| Q# | little-endian (LittleEndian type) |
explicit in the type system |
| OpenQASM | little-endian | matches Qiskit |
Qiskit reverses relative to Cirq and PennyLane. A Bell state looks identical; a GHZ state with a single flipped qubit does not, and neither does any measured bitstring.
counts = {k[::-1]: v for k, v in counts.items()} # Qiskit <-> Cirq bitstrings
Building a circuit
Qiskit
from qiskit import QuantumCircuit
qc = QuantumCircuit(2)
qc.h(0); qc.cx(0, 1); qc.measure_all()
Cirq
import cirq
q = cirq.LineQubit.range(2)
c = cirq.Circuit([cirq.H(q[0]), cirq.CNOT(q[0], q[1]), cirq.measure(*q, key="m")])
PennyLane
import pennylane as qml
dev = qml.device("default.qubit", wires=2)
@qml.qnode(dev)
def circuit():
qml.Hadamard(wires=0)
qml.CNOT(wires=[0, 1])
return qml.probs(wires=[0, 1])
Q#
operation Bell() : Result[] {
use q = Qubit[2];
H(q[0]);
CNOT(q[0], q[1]);
return [MResetZ(q[0]), MResetZ(q[1])];
}
Braket
from braket.circuits import Circuit
c = Circuit().h(0).cnot(0, 1)
Gate names
| Gate | Qiskit | Cirq | PennyLane | Q# | Braket | OpenQASM |
|---|---|---|---|---|---|---|
| Hadamard | h |
H |
Hadamard |
H |
h |
h |
| Pauli X | x |
X |
PauliX |
X |
x |
x |
| Pauli Y | y |
Y |
PauliY |
Y |
y |
y |
| Pauli Z | z |
Z |
PauliZ |
Z |
z |
z |
| S | s |
S |
S |
S |
s |
s |
| T | t |
T |
T |
T |
t |
t |
| $R_x$ | rx |
rx |
RX |
Rx |
rx |
rx |
| $R_y$ | ry |
ry |
RY |
Ry |
ry |
ry |
| $R_z$ | rz |
rz |
RZ |
Rz |
rz |
rz |
| Phase | p |
Z**t |
PhaseShift |
R1 |
phaseshift |
p |
| CNOT | cx |
CNOT |
CNOT |
CNOT |
cnot |
cx |
| CZ | cz |
CZ |
CZ |
CZ |
cz |
cz |
| SWAP | swap |
SWAP |
SWAP |
SWAP |
swap |
swap |
| Toffoli | ccx |
TOFFOLI |
Toffoli |
CCNOT |
ccnot |
ccx |
| $\sqrt{X}$ | sx |
X**0.5 |
SX |
— | v |
sx |
⚠️
pvsrzis not the same gate. They differ by a global phase, which becomes relative and observable the moment either is controlled. Chapter 3 §3.6 measured this.
Execution
| Task | Qiskit | Cirq | PennyLane | Braket |
|---|---|---|---|---|
| Simulator | AerSimulator() |
cirq.Simulator() |
qml.device("default.qubit") |
LocalSimulator() |
| Run | sim.run(qc, shots=N) |
sim.run(c, repetitions=N) |
call the QNode | device.run(c, shots=N) |
| Counts | .result().get_counts() |
.histogram(key="m") |
qml.counts() |
.result().measurement_counts |
| Statevector | Statevector.from_instruction(qc) |
sim.simulate(c).final_state_vector |
qml.state() |
.result().values |
Shots keyword differs: Qiskit shots=, Cirq repetitions=, PennyLane shots= on the device,
Braket shots=.
Parameters
| Qiskit | Cirq | PennyLane | |
|---|---|---|---|
| Declare | Parameter("θ") |
sympy.Symbol("θ") |
plain Python argument |
| Bind | qc.assign_parameters({θ: 0.5}) |
cirq.ParamResolver({"θ": 0.5}) |
pass to the QNode |
| Sweep | list comprehension | cirq.Linspace("θ", 0, π, 10) |
array argument |
PennyLane's model is different in kind: parameters are just function arguments, and gradients come from autodiff. That is the whole reason Part VI uses it.
⚠️ PennyLane gradients return shape
(0,)unless parameters arepennylane.numpyarrays withrequires_grad=True. Chapter 32 lost time to this.
Observables
Qiskit
from qiskit.quantum_info import SparsePauliOp
H = SparsePauliOp.from_list([("ZZ", 1.0), ("XI", 0.5)])
Cirq
H = cirq.Z(q[0]) * cirq.Z(q[1]) + 0.5 * cirq.X(q[0])
PennyLane
H = qml.Hamiltonian([1.0, 0.5], [qml.PauliZ(0) @ qml.PauliZ(1), qml.PauliX(0)])
Qubit identity
- Qiskit — integer indices into a register.
- Cirq —
LineQubit,GridQubit,NamedQubit; qubits are objects with device-relevant identity, which is why Cirq circuits carry topology naturally. - PennyLane —
wires, which may be integers or strings. - Q# — allocated in a
useblock, released automatically, and must be returned to $|0\rangle$.
Scheduling
Cirq's Moment has no Qiskit equivalent. A Cirq circuit is an explicit list of simultaneous
operations; a Qiskit circuit is a list of instructions that the transpiler schedules. Converting Cirq →
Qiskit loses the explicit timing, which Chapter 18 measured as the largest single translation loss.
What does not translate
| Feature | Notes |
|---|---|
Cirq Moment structure |
Lost to Qiskit; recovered only by re-scheduling |
Q# Adjoint / Controlled functors |
No equivalent; must be written out |
| PennyLane autodiff graph | Circuit translates, differentiability does not |
Braket verbatim boxes |
Provider-specific compilation control |
| Custom pulse calibrations | Provider-specific, and qiskit.pulse was removed in 2.0 |
| Mid-circuit measurement + feedforward | Supported unevenly; check the target |
OpenQASM as the bridge
from qiskit.qasm3 import dumps, loads
qasm = dumps(qc)
qc2 = loads(qasm)
Chapter 6 measured round-trip fidelity, and Chapter 18 measured where it fails: custom gates, pulse-level detail, and classical control flow are the lossy parts. For plain gate sequences it is reliable.
See also: Chapter 14 (Cirq), 15 (Q#), 16 (PennyLane), 17 (Braket), 18 (interoperability measured), Appendix F (OpenQASM).