Skip to main content

Cram sheet — Section 1: Perform quantum operations

One-screen review layer for this section: short background primers per objective, then the key facts — each backed by an official source or by an executed proof from the question bank.

Define Pauli Operators​

A Pauli label reads right-to-left​

A Pauli label is read RIGHT to LEFT against qubit indices: the rightmost character acts on qubit 0, the leftmost on the highest-index qubit.

from qiskit.quantum_info import Pauli
Pauli('IX') # X on qubit 0, I on qubit 1
Pauli('XZ').to_matrix() # == np.kron(X, Z): X on qubit 1, Z on qubit 0
Pauli('iXX') # phase prefix: '', 'i', '-', '-i'

The object stores the I/X/Y/Z string plus a phase drawn from +1, i, −1, −i. Writing the phase into the label makes a distinct object, so a plain multi-letter label carries no phase and its matrix is the bare tensor product.

Two families of combination methods, and the whole objective lives on telling them apart.

CallResultNotes
a.tensor(b)a ⊗ bcaller a goes to the higher-index subsystem, b to qubit 0
a.expand(b)b ⊗ athe mirror: a sits on subsystem 0
a.dot(b)A·Bplain matrix product
a.compose(b)B·Acircuit order — a acts first
a.compose(b, front=True)A·Bsame as dot

On Paulis the difference is one character: Pauli('X').dot(Pauli('Y')) is iZ, while Pauli('X').compose(Pauli('Y')) is -iZ. The class never silently drops the phase.

Sources: operators-overview · operator-class · quantum_info

SparsePauliOp, and commutation by counting​

Observables are SparsePauliOp — a linear combination of Pauli strings — and that is what the Estimator primitive expects, not a hand-built Pauli.

from qiskit.quantum_info import SparsePauliOp
SparsePauliOp.from_list([("ZZ", 1), ("XX", 0.5)])
SparsePauliOp.from_sparse_list([("ZX", [1, 4], 1)], num_qubits=5) # prints XIIZI

from_list takes (label, coefficient) pairs on full-width labels. from_sparse_list takes (label, qubit_indices, coefficient) plus an explicit num_qubits, and there the label characters pair with the index list left-to-right — the little-endian display rule read back at you. simplify() drops only terms whose coefficient is zero within tolerance.

Commutation is a counting exercise, not a matrix multiplication: two multi-qubit Paulis commute exactly when the number of positions at which their single-qubit factors anticommute is EVEN.

PairAnticommuting positionsa.commutes(b)
Pauli('XX') vs Pauli('ZZ') (X⊗X vs Z⊗Z)2 (even)True
Pauli('XI') vs Pauli('ZZ') (X⊗I vs Z⊗Z)1 (odd)False

An identity factor contributes zero to that count — it never buys commutation by itself.

Sources: operators-overview · quantum_info

Must know:

  • Pauli('IX') is I ⊗ X — X on qubit 0, identity on qubit 1 — and indexing the object returns the qubit-0 factor first. — ⚙️ proven in s1-q012
  • Pauli products keep their phase: X·Y = iZ, Y·Z = iX, Z·X = iY via .dot; reversing the operands conjugates it (Y·X = -iZ, Z·Y = -iX). — ⚙️ proven in s1-q014
  • SparsePauliOp.simplify() SUMS duplicate terms' coefficients (Z with 1 and Z with 2 becomes Z with 3), never averaging them or keeping only the largest. — ⚙️ proven in s1-q018
  • cx(0, 1); rz(θ, 1); cx(0, 1) is exactly the ZZ evolution PauliEvolutionGate(SparsePauliOp('ZZ'), time=θ/2) — rz(π/3) in the middle equals time=π/6, because RZ also carries the half-angle convention. — ⚙️ proven in s1-q065

Traps:

  • Z on qubit 0 is Pauli('IZ'); Pauli('ZI') is the opposite operator, and .adjoint() does not reverse qubit order (Hermitian labels come back unchanged). — ⚙️ proven in s1-q020
  • PauliEvolutionGate(op, time=t) is exp(−i·t·op) with NO factor ½, while RXXGate(θ) and RZZGate(θ) mean exp(−i·θ/2·P): XX evolution for t = π/6 on |00⟩ gives {'00': 0.75, '11': 0.25}, but RXXGate(π/6) only 0.933/0.067. So evolution time t matches an RXX/RZZ angle of 2t. — ⚙️ proven in s1-q064
  • a @ b on SparsePauliOps is the operator PRODUCT, not a tensor product: (X + Z) @ (X + Z) has raw terms I, −iY, +iY, I, and .simplify() leaves 2·I because the anticommuting cross terms cancel. a.tensor(a) is the different two-qubit XX + XZ + ZX + ZZ. — ⚙️ proven in s1-q057

Apply quantum operations​

Statevector: build it, evolve it, read it​

Statevector is the simulator you carry in your head, and it runs UNITARY circuits only.

from qiskit.quantum_info import Statevector
Statevector.from_label('10') # |q1 q0⟩ — amplitude 1 at index 2
Statevector.from_int(2, dims=4) # same state; an int dims is the TOTAL dimension
sv = Statevector(qc) # delegates to Statevector.from_instruction(qc)
sv2 = sv.evolve(op) # returns a NEW Statevector; sv is untouched

from_label also accepts the eigenstate letters '+', '−', 'r', 'l'. Leave a measure in the circuit and both constructors raise QiskitError: Cannot apply instruction with classical bits: measure. evolve accepts a Pauli, an Operator, a gate or a circuit.

Read-out splits into families that questions love to swap.

CallReturnsNote
.datacomplex amplitudesin index order
.probabilities()ndarraysquared moduli, index order
.probabilities_dict()dict keyed by bitstringkeys run q_(n−1)…q_0
.probabilities([0])ndarray of length 2 — not a dictmarginalizes every qubit not listed; a Bell state gives 0.5/0.5
.expectation_value(obs)numbertakes a Pauli or a SparsePauliOp

For |00⟩ against Z⊗Z + 0.5·(X⊗X) the answer is 1.0: |00⟩ is a +1 eigenstate of ZZ, while XX maps it to the orthogonal |11⟩.

Sources: quantum_info · operator-class

The phase family, and what equality means​

The phase gates leave probabilities untouched and differ only in where the phase lands.

GateMatrixPhase on the 1 amplitude
zdiag(1, −1)−1
sdiag(1, i)+i — the square root of Z, identical to p(π/2)
sdgdiag(1, −i)−i, the conjugate of s
tdiag(1, e^(iπ/4))the square root of S
tdgdiag(1, e^(−iπ/4))the conjugate of t
p(θ)diag(1, e^(iθ))the whole angle
rz(θ)diag(e^(−iθ/2), e^(+iθ/2))split symmetrically over both amplitudes

So after h, s gives (|0⟩ + i|1⟩)/√2 and sdg gives (|0⟩ − i|1⟩)/√2. RZ and P of the same angle differ by a global phase, never by physics: rz(π/2) on |0⟩ records [0.7071−0.7071j, 0] while the probabilities stay [1.0, 0.0]. T on |1⟩ likewise records 0.7071+0.7071j — Qiskit keeps a global phase explicitly. That distinction is exactly what Operator.equiv and == separate. Setting qc.global_phase yourself is unobservable in the same way: exact probabilities and seeded counts come back bit-identical.

Two memorized formulas answer most probability questions without a simulator.

For RY(θ) on the zero stateValue
amplitudescos(θ/2) and sin(θ/2)
P(outcome 1)sin²(θ/2)
⟨Z⟩cos θ — 0.5 at θ = π/3, not sin or cos of the half angle

Sources: qiskit.circuit.library.SGate · operator-class

Must know:

  • Operator(qc) for H on qubit 0 of a two-qubit circuit is I ⊗ H — qubit 0 sits on the RIGHT of the tensor product. — ⚙️ proven in s1-q037
  • efficient_su2(n, reps=r) has 2·n·(r+1) free parameters — an ry and an rz per qubit, with one rotation layer MORE than reps: efficient_su2(4, reps=2) gives 24, and skip_final_rotation_layer=True drops it to 16. — ⚙️ proven in s1-q048
  • real_amplitudes is the RY-only sibling of efficient_su2: its gate set is exactly ry + cx, so n·(reps+1) parameters (12 at the default reps=3 on 3 qubits). quantum_volume carries no free parameters at all. — ⚙️ proven in s1-q050
  • QFTGate(n) takes only a qubit count (no do_swaps, no approximation_degree). One decompose() gives n h, n(n−1)/2 cp and ⌊n/2⌋ swap — for n = 4 count_ops() gives {'cp': 6, 'h': 4, 'swap': 2} (sorted by count), never cx. — ⚙️ proven in s1-q049
  • Reading a Bloch drawing: h puts the arrow on +x and the phase gate then swings it inside the equator — s to +y, sdg to −y. An arrow left at a pole means that qubit was never rotated off the z axis. — ⚙️ proven in s1-q051
  • A plot_state_city figure IS the density matrix: left panel Re(ρ), right panel Im(ρ). After h(0); cx(0,1); s(1) the two 0.5 populations stay real while the |00⟩⟨11| cell becomes −0.5i, so s is visible only in the imaginary panel. — ⚙️ proven in s1-q052

Traps:

  • On a measured circuit, Statevector(qc) and from_instruction offer no shots/backend parameter, and appending a save instruction does not avoid the QiskitError. — ⚙️ proven in s1-q036
  • X on qubit 0 of a two-qubit circuit gives the probabilities_dict() key '01' with probability 1.0 — never '10', never an integer index. — ⚙️ proven in s1-q031
  • Operator.equiv ignores a global phase and == does not: Z versus RZ(π) is equiv-True but ==-False; T versus RZ(π/2) is False both ways (that pairing is S). — ⚙️ proven in s1-q033
Exam checklist
  • Read every Pauli label right-to-left and name the operator on each qubit before answering.
  • Say which of tensor, expand, dot or compose a question used, and which operand acts first.
  • Count anticommuting positions to settle commutation: an even count commutes, an odd count does not.
  • Delete every measure before handing a circuit to Statevector.
  • Check whether the answer wants amplitudes, a probability ndarray, or a bitstring-keyed dict.
  • Write out diag(1, i) for S and diag(e^(−iθ/2), e^(+iθ/2)) for RZ before comparing operators.
  • Ask whether a difference is only a global phase: equiv ignores it, == does not.
  • Compute 2·n·(reps+1) for efficient_su2 and n·(reps+1) for real_amplitudes.

Every fact above is sourced: 📖 links go to official documentation, ⚙️ marks facts observed by executing code against the pinned Qiskit stack (the linked section page shows the proof evidence on its practice questions).