Skip to content

Python API

Simulator

Interactive simulator.

Missing qubits are automatically appended in the all-zero state before a mutating operation.

__init__

__init__(*, seed: int | None = None) -> None

Create an empty interactive simulator.

Missing qubits are automatically appended in the all-zero state before a mutating operation.

Parameters:

Name Type Description Default
seed int | None

Optional unsigned 64-bit seed for measurements and noise.

None

Examples:

from merlin_sim import Simulator

sim = Simulator(seed=1234)

num_qubits property

num_qubits: int

Number of currently tracked qubits.

Examples:

Mutating a previously untracked target grows the simulator.

from merlin_sim import Simulator

sim = Simulator()
sim.reset_x(2)
assert sim.num_qubits == 3

x

x(*targets: int) -> None

Apply Pauli X sequentially to each target.

Raises:

Type Description
IndexError

If a target is negative.

y

y(*targets: int) -> None

Apply Pauli Y sequentially to each target.

Raises:

Type Description
IndexError

If a target is negative.

z

z(*targets: int) -> None

Apply Pauli Z sequentially to each target.

Raises:

Type Description
IndexError

If a target is negative.

s

s(*targets: int) -> None

Apply the phase gate S sequentially to each target.

Raises:

Type Description
IndexError

If a target is negative.

s_dag

s_dag(*targets: int) -> None

Apply S-dagger sequentially to each target.

Raises:

Type Description
IndexError

If a target is negative.

t

t(*targets: int) -> None

Apply a T gate sequentially to each target.

Raises:

Type Description
IndexError

If a target is negative.

t_dag

t_dag(*targets: int) -> None

Apply a T-dagger gate sequentially to each target.

Raises:

Type Description
IndexError

If a target is negative.

cnot

cnot(*targets: int) -> None

Apply CNOTs to consecutive control-target pairs.

All targets are validated before the simulator grows or mutates.

Raises:

Type Description
ValueError

If there is an odd target count or a pair repeats a qubit.

IndexError

If a target is negative.

cx

cx(*targets: int) -> None

Exact alias of cnot.

cz

cz(*targets: int) -> None

Apply controlled-Z gates to consecutive target pairs.

Raises:

Type Description
ValueError

If there is an odd target count or a pair repeats a qubit.

IndexError

If a target is negative.

swap

swap(*targets: int) -> None

Swap consecutive target pairs.

Raises:

Type Description
ValueError

If there is an odd target count or a pair repeats a qubit.

IndexError

If a target is negative.

ccz

ccz(*targets: int) -> None

Apply CCZ gates to consecutive target triples.

Raises:

Type Description
ValueError

If the target count is not divisible by three or a triple repeats a qubit.

IndexError

If a target is negative.

measure

measure(target: int) -> bool

Measure one qubit in the Z basis and collapse the state.

Returns:

Type Description
bool

False for the +1 eigenvalue and True for the -1 eigenvalue.

Raises:

Type Description
IndexError

If target is negative.

Examples:

from merlin_sim import Simulator

sim = Simulator(seed=1234)
sim.reset_x(0)
bit = sim.measure(0)

measure_z

measure_z(target: int) -> bool

Exact alias of measure.

Examples:

from merlin_sim import Simulator

sim = Simulator()
assert sim.measure_z(0) is False

measure_many

measure_many(*targets: int) -> list[bool]

Measure targets sequentially in the Z basis.

Raises:

Type Description
IndexError

If a target is negative.

Examples:

from merlin_sim import Simulator

sim = Simulator()
bits = sim.measure_many(0, 1, 2)
assert bits == [False, False, False]

measure_x

measure_x(target: int) -> bool

Measure one qubit in the X basis and collapse the state.

Raises:

Type Description
IndexError

If target is negative.

MeasurementError

If the projected state is not supported by the DCP representation. Failure does not mutate state or RNG.

Examples:

from merlin_sim import Simulator

sim = Simulator()
sim.reset_x(0)
assert sim.measure_x(0) is False

measure_observable

measure_observable(observable: str, *, flip_probability: float = 0.0) -> bool

Measure a signed Hermitian Pauli product.

The compact observable syntax has an optional leading sign followed by positional X, Y, Z, I, or _ factors, for example -X_YZ. Readout flipping occurs after a successful physical projection.

Parameters:

Name Type Description Default
observable str

Compact positional Pauli string.

required
flip_probability float

Probability of flipping the reported bit.

0.0

Raises:

Type Description
ValueError

If the observable or probability is invalid.

MeasurementError

If the projection is incompatible. Failure does not mutate state or RNG.

Examples:

from merlin_sim import Simulator

sim = Simulator(seed=1234)
sim.reset_x(0, 1)
bit = sim.measure_observable("-XX", flip_probability=0.01)

peek_observable_expectation

peek_observable_expectation(observable: str) -> float

Return a Pauli expectation without mutating state, size, or RNG.

Untracked factors are evaluated against implicit all-zero qubits. The result is a general floating-point value in [-1, 1].

Raises:

Type Description
ValueError

If observable is malformed.

Examples:

from merlin_sim import Simulator

sim = Simulator()
sim.reset_x(0)
assert sim.peek_observable_expectation("X") == 1.0

peek_probability

peek_probability(target: int) -> float

Return the probability that a Z measurement reports True.

This does not mutate state, grow the simulator, or consume randomness. An untracked target has probability zero.

Raises:

Type Description
IndexError

If target is negative.

Examples:

peek_z_probability is an exact alias.

from merlin_sim import Simulator

sim = Simulator()
sim.reset_x(0)
p_true = sim.peek_probability(0)
assert sim.peek_z_probability(0) == p_true == 0.5

peek_z_probability

peek_z_probability(target: int) -> float

Exact alias of peek_probability.

Examples:

from merlin_sim import Simulator

sim = Simulator()
assert sim.peek_z_probability(0) == sim.peek_probability(0)

peek_x_probability

peek_x_probability(target: int) -> float

Return the probability that an X measurement reports True.

Raises:

Type Description
IndexError

If target is negative.

MeasurementError

If the corresponding X measurement is incompatible with the DCP representation.

Examples:

from merlin_sim import Simulator

sim = Simulator()
sim.reset_x(0)
assert sim.peek_x_probability(0) == 0.0

x_error

x_error(*targets: int, p: float) -> None

Independently apply X to each target with probability p.

Raises:

Type Description
ValueError

If p is not finite or is outside [0, 1].

IndexError

If a target is negative.

Examples:

from merlin_sim import Simulator

sim = Simulator(seed=1234)
sim.x_error(0, p=0.01)

y_error

y_error(*targets: int, p: float) -> None

Independently apply Y to each target with probability p.

Raises:

Type Description
ValueError

If p is not finite or is outside [0, 1].

IndexError

If a target is negative.

Examples:

from merlin_sim import Simulator

sim = Simulator(seed=1234)
sim.y_error(0, p=0.01)

z_error

z_error(*targets: int, p: float) -> None

Independently apply Z to each target with probability p.

Raises:

Type Description
ValueError

If p is not finite or is outside [0, 1].

IndexError

If a target is negative.

Examples:

from merlin_sim import Simulator

sim = Simulator(seed=1234)
sim.z_error(0, p=0.01)

depolarize1

depolarize1(*targets: int, p: float) -> None

Apply uniform single-qubit depolarizing noise to each target.

Raises:

Type Description
ValueError

If p is not finite or is outside [0, 1].

IndexError

If a target is negative.

Examples:

from merlin_sim import Simulator

sim = Simulator(seed=1234)
sim.depolarize1(0, 1, 2, p=0.02)

depolarize2

depolarize2(*targets: int, p: float) -> None

Apply uniform two-qubit depolarizing noise to consecutive pairs.

The non-identity Pauli product is chosen uniformly from 15 choices.

Raises:

Type Description
ValueError

If p is invalid, the target count is odd, or a pair uses the same qubit twice.

IndexError

If a target is negative.

Examples:

from merlin_sim import Simulator

sim = Simulator(seed=1234)
sim.depolarize2(0, 1, 2, 3, p=0.03)

depolarize3

depolarize3(*targets: int, p: float) -> None

Apply uniform three-qubit depolarizing noise to target triples.

The non-identity Pauli product is chosen uniformly from 63 choices.

Raises:

Type Description
ValueError

If p is invalid, the target count is not divisible by three, or a triple repeats a qubit.

IndexError

If a target is negative.

Examples:

from merlin_sim import Simulator

sim = Simulator(seed=1234)
sim.depolarize3(0, 1, 2, 3, 4, 5, p=0.03)

state_vector

state_vector(*, endian: Literal['little', 'big'] = 'little') -> NDArray[np.complex64]

Return a normalized, canonical-phase state vector.

Parameters:

Name Type Description Default
endian Literal['little', 'big']

Whether qubit 0 is the least- or most-significant index bit.

'little'

Returns:

Type Description
NDArray[complex64]

A one-dimensional NumPy complex64 array.

This query does not mutate state or consume randomness.

Raises:

Type Description
ValueError

If endian is not "little" or "big".

Examples:

from merlin_sim import Simulator

sim = Simulator()
sim.reset_x(0)
little_endian = sim.state_vector()
big_endian = sim.state_vector(endian="big")

reset

reset(*targets: int) -> None

Reset targets sequentially to the +1 Z eigenstate |0>.

Reset measurements can consume randomness, but their results are not reported.

Raises:

Type Description
IndexError

If a target is negative.

Examples:

from merlin_sim import Simulator

sim = Simulator(seed=1234)
sim.reset(0)

reset_z

reset_z(*targets: int) -> None

Exact alias of reset.

Examples:

from merlin_sim import Simulator

sim = Simulator(seed=1234)
sim.reset_z(0)

reset_x

reset_x(*targets: int) -> None

Reset targets sequentially to the +1 X eigenstate |+>.

Raises:

Type Description
IndexError

If a target is negative.

Examples:

from merlin_sim import Simulator

sim = Simulator(seed=1234)
sim.reset_x(0)

copy

copy(*, copy_rng: bool = False, seed: int | None = None) -> Self

Copy the quantum state with configurable RNG behavior.

By default the copy receives a fresh entropy-seeded RNG. Set copy_rng=True to reproduce the exact RNG state, or pass seed to use a chosen new stream.

Raises:

Type Description
ValueError

If both copy_rng=True and seed are supplied.

Examples:

from merlin_sim import Simulator

sim = Simulator(seed=1234)
quantum_copy = sim.copy()
exact_copy = sim.copy(copy_rng=True)
seeded_copy = sim.copy(seed=5678)

CircuitSampler

Parse-once sampler backed by Simulator.

A private noiseless replay is performed during construction to define the detector and observable reference parities. Every attempted shot otherwise starts from a fresh all-zero state and executes the circuit in source order.

__init__

__init__(circuit: str, *, seed: int | None = None, postselect: Sequence[int] | None = None) -> None

Create a sampler and eagerly compute its noiseless reference.

The circuit is parsed once. Each attempted shot then starts from a fresh all-zero Simulator and executes the circuit in source order.

Parameters:

Name Type Description Default
circuit str

Circuit source in the supported extended Stim format.

required
seed int | None

Optional unsigned 64-bit seed for reproducible sampling.

None
postselect Sequence[int] | None

Detector indices whose normalized True value rejects a shot. Indices must be unique and in range.

None

Raises:

Type Description
ValueError

If the circuit or postselection mask is invalid.

MeasurementError

If noiseless reference generation encounters an incompatible measurement.

num_qubits property

num_qubits: int

Number of logical qubits referenced by the circuit.

num_measurements property

num_measurements: int

Number of measurement-record columns in a complete shot.

num_detectors property

num_detectors: int

Number of detector columns.

num_observables property

num_observables: int

Number of logical-observable columns.

num_exp_vals property

num_exp_vals: int

Number of expectation-value columns produced by EXP_VAL.

num_fault_sites property

num_fault_sites: int

Number of explicit noise and readout-fault sites.

Batched single-qubit noise targets count separately, while two- and three-qubit noise groups count once. A measurement target is counted only when its readout probability was written explicitly, including an explicit probability of zero.

sample

sample(shots: int, *, bit_packed: bool = False, num_faults: int | None = None) -> SampleResult

Sample the circuit.

Parameters:

Name Type Description Default
shots int

Number of shots to attempt. Rejected postselected attempts are not replaced.

required
bit_packed bool

Pack each boolean output family independently into little-endian uint8 rows. exp_vals remains float64.

False
num_faults int | None

Sample uniformly from executions where exactly num_faults happen. Assumes that all fault sites have uniform proability and does not support PAULI_CHANNEL_1 or PAULI_CHANNEL_2 instructions.

None

Returns:

Type Description
SampleResult

Measurement records, detector flips, observable flips, expectation

SampleResult

values, and attempted/accepted shot counts. Postselected attempts

SampleResult

are stopped at the rejecting detector and omitted from all arrays.

SampleResult

Successive calls continue the sampler's RNG stream.

Raises:

Type Description
ValueError

If shots or num_faults is negative.

MeasurementError

If a shot encounters an incompatible measurement. Randomness consumed before the failure remains consumed.

Examples:

from merlin_sim import CircuitSampler

sampler = CircuitSampler(
    '''
    RX 0
    T 0
    EXP_VAL X0 Z0
    MX 0
    DETECTOR rec[-1]
    OBSERVABLE_INCLUDE(0) rec[-1]
    ''',
    seed=1234,
    postselect=[0],
)

assert sampler.num_qubits == 1
assert sampler.num_measurements == 1
assert sampler.num_detectors == 1
assert sampler.num_observables == 1
assert sampler.num_exp_vals == 2

result = sampler.sample(100)
assert result.total_shots == 100
assert result.discards == result.total_shots - result.accepted_shots
assert result.measurements.shape == (result.accepted_shots, 1)
assert result.detectors.shape == (result.accepted_shots, 1)
assert result.observables.shape == (result.accepted_shots, 1)
assert result.exp_vals.shape == (result.accepted_shots, 2)

packed = sampler.sample(100, bit_packed=True)
assert packed.measurements.dtype.name == "uint8"

See Circuit instructions for the complete accepted circuit language.

SampleResult

Results returned by CircuitSampler.sample.

Each array has one row per accepted shot. With unpacked sampling the three bit arrays have shape (accepted_shots, count) and NumPy bool dtype. With bit-packed sampling they have shape (accepted_shots, ceil(count / 8)), uint8 dtype, and independently padded little-endian rows. Expectation values are never packed and have shape (accepted_shots, num_exp_vals).

measurements property

measurements: NDArray[bool_] | NDArray[uint8]

Raw measurement records in circuit order.

detectors property

detectors: NDArray[bool_] | NDArray[uint8]

Detector flips relative to the noiseless reference sample.

observables property

observables: NDArray[bool_] | NDArray[uint8]

Logical-observable flips relative to the noiseless reference.

exp_vals property

exp_vals: NDArray[float64]

Expectation values from EXP_VAL, with NumPy float64 dtype.

total_shots property

total_shots: int

Number of attempted shots requested from the sampler.

accepted_shots property

accepted_shots: int

Number of rows that survived detector postselection.

discards property

discards: int

Number of attempted shots rejected by postselection.