Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Circuit

Constructor

Circuit(num_qubits) — an empty circuit.

Conventions

One gate per channel. Every Circuit method pushes exactly one channel per qubit/pair — a broadcast call like depolarize(p, [0, 1]) pushes two channels, not one bundled channel. Truncation runs after every channel, never after a bundle, so fusing gates changes the answer; build circuits one gate per call — see Truncation.

Rotation angle convention. Every rotation method implements U = exp(-i*theta*P/2) for its generator P. A generator coefficient s in exp(-i*s*P) (no /2) needs theta = 2*s.

Gates

Each of these is a Circuit method, a gates.<name>(...) factory returning a Channel for circuit.append(...), or both:

Circuit methodgates. factoryQubitsExtra argsCaveats
.h(qubit)gates.h(qubit)1——
.s(qubit)gates.s(qubit)1——
.sdg(qubit)gates.sdg(qubit)1S^dagger—
.x(qubit)gates.x(qubit)1——
.y(qubit)gates.y(qubit)1——
.z(qubit)gates.z(qubit)1——
.cnot(control, target)gates.cnot(control, target)2indices must differ—
.cz(q0, q1)gates.cz(q0, q1)2indices must differ—
.swap(q0, q1)gates.swap(q0, q1)2indices must differ—
.rz(theta, qubit)gates.rz(theta, qubit)1theta—
.rx(theta, qubit)gates.rx(theta, qubit)1theta—
.ry(theta, qubit)gates.ry(theta, qubit)1theta—
.pauli_rotation(pauli, qubits, theta)gates.pauli_rotation(pauli, qubits, theta)anypauli[k] acts on qubits[k]; identity positions are expressed by omission, not Ithe one exemption from the two-qubit support panic below — any generator weight is accepted
.unitary_1q(qubit, matrix)gates.unitary_1q(qubit, matrix)12x2 complex; ValueError unless every entry of U†U − I is within 1e-10—
.unitary_2q(q0, q1, matrix)gates.unitary_2q(q0, q1, matrix)24x4 complex, unitary to 1e-10 as above; q0 is the more significant tensor factor (|q0 q1>)—

A channel with support on more than two qubits, other than pauli_rotation, makes propagate panic; there is no fallback path.

circuit.append(gates.h(0)) is equivalent to circuit.h(0).

Noise channels

Circuit methodnoise. factorySemanticsCaveats
.depolarize(p, qubits)noise.depolarize(p, qubit)one channel per qubit in qubitsnon-unitary; see below
.dephase(p, qubits)noise.dephase(p, qubit)one channel per qubitnon-unitary; see below
.amplitude_damping(gamma, qubits)noise.amplitude_damping(gamma, qubit)one channel per qubitnon-unitary; see below
.pauli_channel(px, py, pz, qubits)noise.pauli_channel(px, py, pz, qubit)px + py + pz <= 1; one channel per qubitnon-unitary; see below
.depolarize2(p, pairs)noise.depolarize2(p, q0, q1)one channel per (q0, q1) pair; indices in a pair must differnon-unitary; see below

Every noise channel is non-unitary: circuit.adjoint() raises ValueError naming it if the circuit contains one.

Coefficient rescale per channel: depolarize(p) scales every non-identity Pauli on its qubit by 1 - 4p/3; dephase(p) scales X and Y by 1 - 2p and leaves Z untouched; pauli_channel(px, py, pz) scales X by 1 - 2(py + pz), Y by 1 - 2(px + pz), Z by 1 - 2(px + py); depolarize2(p) scales every non-identity two-qubit Pauli on its pair by 1 - 16p/15.

pauli_channel(p/3, p/3, p/3, q) is depolarize(p, q); pauli_channel(0, 0, p, q) is dephase(p, q). See Noise channels for a worked example.

Composition

OperationEffect
len(circuit)channel count
circuit[i]the channel at i (a Channel)
circuit[a:b]a new Circuit of the selected channels, same width; every slice form works, including a negative step (which reverses channel order, not the adjoint)
circuit.append(channel)push one Channel from a gates/noise factory
a.extend(b)append every channel of b to a, in place; both must share num_qubits
a + bnew circuit: a’s channels then b’s; neither operand is modified
circuit.adjoint()new circuit: reversed channel order, each gate replaced by its dagger
circuit.gatesthe channel list as a list of task-JSON schema-v1 gate dicts

circuit.adjoint() satisfies obs.propagate(c, direction="heisenberg") == obs.propagate(c.adjoint(), direction="forward") to floating-point tolerance. Per gate: rz/rx/ry/pauli_rotation negate theta, s becomes sdg (and back), unitary_1q/unitary_2q conjugate-transpose, and h/x/y/z/cnot/cz/swap are self-adjoint. A non-unitary channel (a noise channel) raises ValueError naming it.

Importing a circuit

CallSource
paulistrings.interop.circuit_from_stim(src)a stim circuit; returns (Circuit, PauliSum | None)
paulistrings.interop.circuit_from_qiskit(qc)a qiskit.QuantumCircuit
paulistrings.interop.circuit_from_json(obj, n_qubits)the task-JSON schema-v1 "circuit" object
paulistrings.interop.load_task(path)a whole task-JSON schema-v1 file (or an already-parsed dict), returned as a Task dataclass with .circuit, .observable, .truncation, .direction, .threads, .state and the raw dict

See Importing circuits for the recipe.