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

propagate / propagate_with_stats

sum.propagate(circuit, policy=None, direction=None, target_bucket_len=1024,
               min_buckets=128, partitions=None, pin_memory=True,
               partition_row_seed=None, partition_row_blocks=None,
               partition_row_exclude=None, comm=None, result="gather",
               device=None) -> PauliSum

sum.propagate_with_stats(circuit, policy=None, direction=None, target_bucket_len=1024,
                          min_buckets=128, partitions=None, pin_memory=True,
                          partition_row_seed=None, partition_row_blocks=None,
                          partition_row_exclude=None, comm=None, result="gather",
                          device=None)
    -> (PauliSum, PropagationStats)

propagate_with_stats takes the same arguments and returns the same evolved sum (to floating-point tolerance) as propagate, plus a PropagationStats recording per-layer counts. The GIL is released for the duration of both calls.

Parameters

ParameterTypeDefaultMeaning
circuitCircuitrequiredmust share num_qubits with sum
policyTruncation | NoneNoneNone applies no per-term filtering beyond the engine’s exact-zero drop
direction"forward" | "heisenberg" | NoneNone = "forward"the default is not the Heisenberg picture most examples here use — pass it explicitly; see Direction semantics
target_bucket_lenint1024engine’s per-layer bucket-sizing knob; at least 1, else ValueError
min_bucketsint128engine’s per-layer bucket-sizing knob; at least 16, else ValueError
partitionsNone | "auto" | int | list[list[int]]Nonesplits the sum across NUMA domains; see below
pin_memoryboolTruebinds each partition’s allocations to its node
partition_row_seedint | NoneNone = the sum’s own hash seedwhich GF(2) rows decide a term’s partition
partition_row_blockslist[list[int]] | NoneNoneexplicit disjoint qubit blocks, one per partition, instead of a seeded draw
partition_row_excludedict[str, list[int]] | NoneNone{"x": [...], "z": [...]}: the seeded draw with these qubits’ x-bits / z-bits read by no row
commmpi4py.MPI.Comm | NoneNonerun one partition per MPI rank instead of partitions
result"gather" | "local""gather"only read under comm=: "gather" returns the whole sum on rank 0 and empty elsewhere, "local" returns each rank’s own disjoint share, a plain PauliSum (to_arrays() reads it into numpy)
deviceNone | int | list[int] | "auto"Nonerun on CUDA devices instead of the host; see below

PauliSum.num_buckets reads back the realized bucket count, which can differ from target_bucket_len/min_buckets since bucketing only ever grows.

partitions

ValuePlacement
None or 1unpartitioned
"auto"one partition per NUMA node (falls back to unpartitioned on a single-node box)
an int power of two >= 2, at most 64caps placement at that many nodes
list[list[int]]explicit disjoint CPU lists, one partition per list

In partitioned mode RAYON_NUM_THREADS and engine are ignored, and truncation.topn raises NotImplementedError (use approx_topn).

partition_row_blocks excludes both partition_row_seed and partition_row_exclude; each of the three needs partitions=, comm= or a list of several devices. partition_row_exclude={"x": sites} keeps strings that differ only in the x-bits of sites on one rank; {"z": sites} does the same for z-bits. Under comm=, the block count in partition_row_blocks must equal the MPI group size, and the blocks must be identical on every rank.

comm= requires MPI_THREAD_SERIALIZED set before importing MPI, a power-of-two rank count, and every rank calling with the same replicated input in the same order. comm= and partitions= are alternatives — place via the launcher (e.g. mpirun --map-by ppr:1:numa --bind-to numa) rather than both. Without the mpi feature, comm= raises RuntimeError.

See NUMA partitions and MPI ranks for recipes.

device

ValuePlacement
Nonethe host engine
an intthe whole sum on that CUDA device ordinal; ValueError if this process cannot see it
list[int]one partition per entry, in partition order; a one-element list is that device
"auto"devices 0..k for the largest power of two k visible, so device 0 alone on a one-GPU box

A list’s length must be a power of two, at most 64, or it is a ValueError; an ordinal may repeat, putting several partitions on one device. Several devices split the sum like partitions=: partition_row_seed and partition_row_blocks pick the rows, the block count equal to the list length. device= is an alternative to partitions=; passing both, or result="local" without comm=, or partition_row_blocks= with a single device, is a ValueError. engine is ignored, truncation.topn runs on one device and raises NotImplementedError on several or under comm= (use approx_topn), an exhausted device raises MemoryError, and without the cuda feature or a visible device device= raises RuntimeError.

With comm=, device= is this rank’s one device: an int or "auto" (a device near the rank’s CPUs, gpu::local_device_for_comm), and a list of several ordinals is a ValueError. result= and the row kwargs mean what they mean on the host comm= path, and a rank that cannot use its device fails the call on every rank. The pair needs the extension built with both the cuda and mpi features, and raises RuntimeError naming the missing one otherwise. See CUDA devices.

GpuPauliSum

sum.to_device(device=0) -> GpuPauliSum

resident.propagate(circuit, policy=None, direction=None,
                   target_bucket_len=1024, min_buckets=128) -> None
resident.propagate_with_stats(circuit, policy=None, direction=None,
                              target_bucket_len=1024, min_buckets=128) -> PropagationStats
resident.to_host() -> PauliSum

A sum resident on one CUDA device: propagate steps it in place, to_host copies it back and leaves it resident. The class exists only in a build with the cuda feature; elsewhere to_device raises RuntimeError. The arguments mean what they mean on PauliSum.propagate, with the same errors. A device error mid-run leaves the sum holding the last completed layer’s output.

AttributeTypeMeaning
len(resident)intterms on the device
.num_qubitsintthe sum’s qubit count
.deviceintthe CUDA device ordinal
.num_bucketsintthe device partition’s current bucket count, grow-only

PropagationStats

One entry per layer applied, in application order (reverse circuit order under direction="heisenberg").

A layer here is one applied channel — one gate, or one noise channel on one qubit — not a brickwork layer of parallel gates in the field-standard sense. A broadcast call such as circuit.depolarize(p, [0, 1]) contributes two layers, and len(circuit) == stats.layers.

FieldTypeMeaning
.layersintnumber of layers (channels) applied
.terms_inlist[int]term count before each layer; terms_in[k+1] == terms_out[k]
.terms_outlist[int]term count after each layer’s truncation
.peak_termsintpeak resident term count between layers (not the transient in-layer expansion)
.final_termsintterms_out[-1], or the input’s count for a zero-layer circuit
.circuit_indexlist[int]this layer’s position in the circuit as written, independent of direction
.application_indexlist[int]this layer’s position in the propagation loop, 0..layers regardless of direction
.gate_namelist[str]the applied channel’s debug name per layer: the PtmChannel constructor name (hadamard, cnot, depolarizing, …; phase_gate_adjoint for sdg) or PauliRotation
.nanoslist[int]elapsed wall-clock nanoseconds per layer; for a partitioned/distributed run, the max over partitions/ranks
.partitionPartitionStatsper-partition detail; one partition for an unpartitioned call
.collapsesint | Nonecollapses a truncation.collapse_sample made during this call, None without one; under comm= rank 0’s count, all-reduced so every rank reports it

PartitionStats

Per-layer, per-partition detail (PropagationStats.partition), one entry per layer in application order.

FieldTypeMeaning
.partitionsintpartition count (power of two); the MPI group size under comm=
.rankint | Nonethis process’s rank under comm=, else None
.sizeint | Nonethe comm= group size, else None
.deviceslist[int] | Nonethe CUDA device of each partition for a device= run (this rank’s one device under comm=), else None
.locallist[bool]whether each layer moved no row across a partition boundary (rows_exported[k] == 0)
.rows_exportedlist[int]rows sent across partition boundaries per layer, summed over sender/receiver pairs
.bytes_exportedlist[int]wire bytes behind rows_exported, including per-block headers
.terms_inlist[list[int]]terms_in[k][r]: terms partition r held before layer k
.terms_outlist[list[int]]terms partition r held after layer k’s truncation
.imbalancelist[float]max/mean of terms_in per layer; 1.0 is perfect balance, partitions is worst case
.nanoslist[list[int]]each partition’s own elapsed wall time per layer; PropagationStats.nanos[k] is max(nanos[k])

Under comm=, a distributed run’s per-layer lists (terms_in, terms_out, rows_exported, bytes_exported, nanos) hold this rank’s own entry only — nothing gathers the group’s counters, since that would add a collective per layer for a diagnostic. Reduce over comm for the group’s picture; imbalance is always 1.0 in that case.