Skip to content

Circuits

qsimlab.circuit

Circuit construction: Circuit, the gate table and instructions.

Conventions (python/API.md §5): qubit q is bit q of a basis-state index (little-endian, as in Qiskit); two-qubit gates take the control first; angles are in radians; measurement k (program order) writes classical bit k.

>>> from qsimlab import Circuit
>>> c = Circuit(3).h(0).cx(0, 1).cx(1, 2).measure_all()
>>> c.stats()["gates_2q"], c.num_measurements
(2, 3)
>>> print(Circuit(2).h(0).cx(0, 1).draw())
q0 : ── [H] ──  ■  ────
q1 : ─────────  X  ────

Instruction

Bases: NamedTuple

One operation of a circuit, as returned by Circuit.instructions.

name is a gate name from GATES, "measure", "reset" or a noise channel; params holds angles (gates) or the probability (noise); c_if is None or (measurement_index, value).

Circuit

Circuit(num_qubits: int)

A quantum circuit on num_qubits qubits, all starting in |0>.

Builder methods return self so they chain. Every gate method accepts c_if= to condition it on a measurement: c_if=k applies the gate if measurement k read 1, c_if=(k, 0) if it read 0.

>>> c = Circuit(2)
>>> c.h(0).measure(0).x(1, c_if=0).measure(1)
Circuit(num_qubits=2, ops=4, measurements=2)

num_qubits property

num_qubits: int

Number of qubits.

num_measurements property

num_measurements: int

Number of measurements (= classical bits).

global_phase property writable

global_phase: float

Global phase in radians; every amplitude is multiplied by exp(1j * global_phase).

readout_error property writable

readout_error: float

Probability that each measurement result is reported flipped (Stim M(p)).

detectors property

detectors: list

Detectors (lists of absolute measurement indices), for QEC circuits.

observables property

observables: list

observables[k]: measurement indices whose parity is logical observable k.

from_instructions classmethod

from_instructions(
    num_qubits: int, instructions: Iterable[Sequence[Any]]
) -> "Circuit"

Builds a circuit from (name, qubits, params[, c_if]) tuples (see instructions).

>>> c = Circuit.from_instructions(2, [("h", (0,), ()), ("cx", (0, 1), ())])
>>> c == Circuit(2).h(0).cx(0, 1)
True

instructions

instructions() -> list

All operations in program order as Instruction tuples.

copy

copy() -> 'Circuit'

An independent copy.

stats

stats() -> dict

Summary counts: gates by arity, depth, Clifford/T counts, measurements, noise, ...

>>> Circuit(2).h(0).t(1).cx(0, 1).stats()["t_gates"]
1

draw

draw() -> str

A text diagram (one line per qubit).

append

append(
    name: str,
    qubits: Union[int, Sequence[int]],
    params: Sequence[float] = (),
    *,
    c_if: Condition = None,
) -> "Circuit"

Appends gate name (any key or alias of GATES).

>>> Circuit(2).append("cp", (0, 1), (0.5,)).instructions()[0].name
'cp'

i

i(q: int, *, c_if: Condition = None) -> 'Circuit'

Identity.

h

h(q: int, *, c_if: Condition = None) -> 'Circuit'

Hadamard.

x

x(q: int, *, c_if: Condition = None) -> 'Circuit'

Pauli X.

y

y(q: int, *, c_if: Condition = None) -> 'Circuit'

Pauli Y.

z

z(q: int, *, c_if: Condition = None) -> 'Circuit'

Pauli Z.

s

s(q: int, *, c_if: Condition = None) -> 'Circuit'

S = diag(1, i).

sdg

sdg(q: int, *, c_if: Condition = None) -> 'Circuit'

S† = diag(1, -i).

t

t(q: int, *, c_if: Condition = None) -> 'Circuit'

T = diag(1, e^{iπ/4}).

tdg

tdg(q: int, *, c_if: Condition = None) -> 'Circuit'

T† = diag(1, e^{-iπ/4}).

sx

sx(q: int, *, c_if: Condition = None) -> 'Circuit'

√X (Qiskit's sx).

sxdg

sxdg(q: int, *, c_if: Condition = None) -> 'Circuit'

(√X)†.

rx

rx(
    q: int, theta: float, *, c_if: Condition = None
) -> "Circuit"

exp(-iθX/2).

ry

ry(
    q: int, theta: float, *, c_if: Condition = None
) -> "Circuit"

exp(-iθY/2).

rz

rz(
    q: int, theta: float, *, c_if: Condition = None
) -> "Circuit"

exp(-iθZ/2).

p

p(
    q: int, theta: float, *, c_if: Condition = None
) -> "Circuit"

Phase gate diag(1, e^{iθ}) (OpenQASM u1/p).

u

u(
    q: int,
    theta: float,
    phi: float,
    lam: float,
    *,
    c_if: Condition = None,
) -> "Circuit"

OpenQASM u3(θ, φ, λ) = [[cos θ/2, -e^{iλ} sin θ/2], [e^{iφ} sin θ/2, e^{i(φ+λ)} cos θ/2]].

cx

cx(
    control: int, target: int, *, c_if: Condition = None
) -> "Circuit"

Controlled X (CNOT).

cz

cz(a: int, b: int, *, c_if: Condition = None) -> 'Circuit'

Controlled Z.

swap

swap(
    a: int, b: int, *, c_if: Condition = None
) -> "Circuit"

SWAP.

iswap

iswap(
    a: int, b: int, *, c_if: Condition = None
) -> "Circuit"

iSWAP (|01> -> i|10>, |10> -> i|01>).

iswapdg

iswapdg(
    a: int, b: int, *, c_if: Condition = None
) -> "Circuit"

iSWAP†.

cp

cp(
    a: int, b: int, theta: float, *, c_if: Condition = None
) -> "Circuit"

Controlled phase diag(1, 1, 1, e^{iθ}).

ccx

ccx(
    c1: int, c2: int, target: int, *, c_if: Condition = None
) -> "Circuit"

Toffoli (doubly controlled X).

measure

measure(*qubits: int) -> 'Circuit'

Measures each qubit in the Z basis; measurement k writes classical bit k.

measure_all

measure_all() -> 'Circuit'

Measures every qubit, in order.

reset

reset(*qubits: int) -> 'Circuit'

Resets each qubit to |0>.

noise

noise(
    channel: str,
    qubits: Union[int, Sequence[int]],
    p: float,
) -> "Circuit"

Appends a stochastic Pauli channel (one of NOISE_CHANNELS) on each qubit (pairs for depolarize2).

x_error

x_error(
    q: Union[int, Sequence[int]], p: float
) -> "Circuit"

X with probability p (Stim X_ERROR).

y_error

y_error(
    q: Union[int, Sequence[int]], p: float
) -> "Circuit"

Y with probability p (Stim Y_ERROR).

z_error

z_error(
    q: Union[int, Sequence[int]], p: float
) -> "Circuit"

Z with probability p (Stim Z_ERROR).

depolarize1

depolarize1(
    q: Union[int, Sequence[int]], p: float
) -> "Circuit"

X, Y or Z, each with probability p/3 (Stim DEPOLARIZE1).

depolarize2

depolarize2(a: int, b: int, p: float) -> 'Circuit'

One of the 15 non-identity two-qubit Paulis, each with probability p/15 (Stim DEPOLARIZE2).

detector

detector(measurements: Iterable[int]) -> int

Declares a detector (parity of absolute measurement indices); returns its index.

observable_include

observable_include(
    index: int, measurements: Iterable[int]
) -> "Circuit"

Adds measurements to logical observable index.

compose

compose(
    other: "Circuit", qubits: Optional[Sequence[int]] = None
) -> "Circuit"

Appends other (its qubit i on qubits[i]) in place; returns self.

Measurement indices in conditionals, detectors and observables of other are shifted past this circuit's measurements.

repeat

repeat(
    body: "Circuit",
    reps: int,
    qubits: Optional[Sequence[int]] = None,
) -> "Circuit"

Appends body reps times (unrolled).

The circuit remembers it contains a repeat block, so qsimlab.simulate runs the repeat-detection pass by default (exact fast paths for repeated Clifford/diagonal/small blocks).

>>> layer = Circuit(2).rx(0, 0.1).cz(0, 1)
>>> len(Circuit(2).repeat(layer, 100))
200

inverse

inverse() -> 'Circuit'

The inverse of a unitary circuit (reversed, each gate inverted, global phase negated).

remove_final_measurements

remove_final_measurements() -> 'Circuit'

A copy without terminal measurements (and without detectors/observables).

to_qasm

to_qasm() -> str

OpenQASM 2.0 source. Conditionals and noise channels have no faithful OpenQASM 2 form and raise UnsupportedOperationError; the global phase is not representable and is dropped.

from_qasm classmethod

from_qasm(source: str) -> 'Circuit'

Parses OpenQASM 2.0: registers (laid out in declaration order), user gate definitions, broadcasting, barrier, reset, measure, if on one-bit registers, and the qelib1.inc gate set plus common Qiskit extensions (rzz, rxx, ryy, rzx, ecr, cu, ...).

>>> src = 'OPENQASM 2.0; include "qelib1.inc"; qreg q[2]; h q[0]; cx q[0],q[1];'
>>> Circuit.from_qasm(src) == Circuit(2).h(0).cx(0, 1)
True

to_stim

to_stim() -> str

Stim circuit text (Clifford gates, measurements, resets, Pauli noise, detectors, observables; readout_error becomes M(p)).

from_stim classmethod

from_stim(source: str) -> 'Circuit'

Parses Stim circuit text (REPEAT blocks are unrolled; DETECTOR and OBSERVABLE_INCLUDE populate detectors / observables).

>>> c = Circuit.from_stim("H 0\nCX 0 1\nM 0 1\nDETECTOR rec[-2] rec[-1]")
>>> c.num_measurements, c.detectors
(2, [[0, 1]])