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 positionsCommute?
X⊗X vs Z⊗Z2 (even)yes
X⊗I vs Z⊗Z1 (odd)no

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

Sources: operators-overview · quantum_info

Must know:

  • Pauli labels are little-endian: 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
  • Pauli('XX').commutes(Pauli('ZZ')) is True (two anticommuting positions, even) and Pauli('XI').commutes(Pauli('ZZ')) is False (one position, odd). — ⚙️ proven in s1-q017
  • SparsePauliOp.simplify() SUMS the coefficients of duplicate terms (Z with 1 and Z with 2 becomes Z with 3) — it never averages them and never keeps only the largest. — ⚙️ proven in s1-q018

Traps:

  • Pauli('XZ').to_matrix() is exactly np.kron(X, Z), so label order and tensor order coincide — the convention stays invisible until a question asks which qubit an operator acts on. — ⚙️ proven in s1-q010
  • To place Z on qubit 0 you must write Pauli('IZ')Pauli('ZI') is the opposite operator, and .adjoint() does not reverse qubit order (Hermitian labels come back unchanged). — ⚙️ proven in s1-q020

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 2marginalizes every qubit not listed
.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 θ

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

Must know:

  • Operator(qc) for an H on qubit 0 of a two-qubit circuit is the 4×4 matrix I ⊗ H — qubit 0 sits on the RIGHT of the tensor product, not the left. — ⚙️ 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 that is {'h': 4, 'cp': 6, 'swap': 2}, 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:

  • Statevector.from_instruction and Statevector(qc) both raise QiskitError: Cannot apply instruction with classical bits: measure on a measured circuit; there is no shots/backend parameter and appending a save instruction does not help. — ⚙️ proven in s1-q036
  • probabilities_dict() keys are q_(n−1)…q_0 bitstrings: applying X to qubit 0 of a two-qubit circuit gives the key '01' with probability 1.0, never '10' and never an integer index. — ⚙️ proven in s1-q031
  • sv.probabilities([0]) marginalizes over the other qubits and returns an ndarray of length 2 (a Bell state gives 0.5/0.5) — not a dict. — ⚙️ proven in s1-q042
  • 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
  • For RY(θ) on |0⟩ the expectation value is ⟨Z⟩ = cos θ — at θ = π/3 that is 0.5, not sin or cos of the half angle. — ⚙️ proven in s1-q040
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).