Skip to content

pyqpanda3 v0.5.0: precision-selectable simulation and a transpiler that pulls ahead

pyqpanda3 0.4.1 made fidelity data drive transpilation decisions and filled the simulator gaps. v0.5.0 asks the next question: when the workflow is correct, how much faster and how much leaner can it get?

The answer runs through the whole stack — arithmetic precision you can choose per simulator, SIMD kernels selected at process start, a rebuilt layout/routing engine, and a bundled allocator. The theme of this release is performance with the dials exposed.

Precision is now a choice, not an accident ​

Every CPU-family simulator takes a precision keyword at construction: float32 halves the memory of the state and roughly doubles kernel throughput; float64 stays the default. The choice is immutable per instance and readable back, so downstream code can adapt:

python
import numpy as np
from pyqpanda3.core import CPUQVM, MPSQVM, H, CNOT, QProg

seed = np.array([0.5, 0.5, 0.5, 0.5], dtype=np.complex64)

qvm32 = CPUQVM(precision="float32")
qvm32.init_state(seed)
print(qvm32.current_state().dtype, qvm32.current_state().shape, qvm32.precision)

qvm64 = CPUQVM()
qvm64.init_state(seed)
print(qvm64.current_state().dtype, qvm64.current_state().shape, qvm64.precision)

mps = MPSQVM(precision="float32")
mps.run(QProg() << H(0) << CNOT(0, 1), 2)
print(mps.current_state().dtype, mps.current_state().shape)

Output:

text
complex64 (4,) float32
complex128 (4,) float64
complex64 (4,)

current_state() is new — it hands you the state as a NumPy array in the native precision, and DensityMatrixSimulator.density_matrix_native() does the same for density matrices. One distinction matters when reading it back: CPUQVM.current_state() mirrors the state you installed with init_state(...) and is not filled in by run() — the state a run() produced comes from result().get_state_vector() — whereas MPSQVM.current_state() is the live post-run state. A wrong precision fails immediately with a dedicated exception (InvalidPrecisionError, UnsupportedPrecisionError), never silently.

Which backends support what? Ask the registry instead of guessing:

python
from pyqpanda3.core import simulator_capabilities

for cap in simulator_capabilities():
    print(f"{cap.name:26s} {','.join(cap.supported_precisions):16s}"
          f" available={str(cap.available):5s} domain={cap.simd_domain}")

Output:

text
CPUQVM                     float32,float64  available=True  domain=StateVector
DensityMatrixSimulator     float32,float64  available=True  domain=Density
GPUQVM                     float64          available=False domain=NotApplicable
MPSQVM                     float32,float64  available=True  domain=Tensor
PartialAmplitudeQVM        float32,float64  available=True  domain=Amplitude
SingleAmplitudeQVM         float32,float64  available=True  domain=Amplitude
SparseHeisenbergSimulator  float32,float64  available=True  domain=Sparse
Stabilizer                 exact            available=True  domain=NotApplicable

That GPUQVM … available=False row is the registry doing its job on a CUDA-less host: construction is refused with BackendUnavailableError up front, not with an obscure import-time failure.

SIMD kernels, chosen at runtime ​

The state-vector kernels are now compiled for several SIMD instruction sets and picked by a runtime detector when your process starts. The new pyqpanda3.core.simd submodule shows the decision and lets you override it:

python
from pyqpanda3.core import simd

cap = simd.capabilities()
print(cap.architecture, list(cap.available_backends), cap.active_backend)

simd.set_preference("scalar")  # reproduce legacy scalar behavior
print(simd.active_backend())
simd.set_preference("auto")
print(simd.active_backend())

Output (on an AVX2-capable x86_64 host):

text
x86_64 ['scalar', 'sse2', 'avx2', 'avx512'] avx2
scalar
avx2

On the release benchmark host the AVX2 kernels are a median 2.13x faster than scalar across the representative suite (~2.5x at float32, ~1.9x at float64). The promotion policy is deliberately conservative: a backend only becomes the automatic choice when its correctness rows pass and its measured runtimes beat the next safe backend — AVX-512 and NEON are always explicitly selectable but never silently auto-promoted without verified evidence.

The LightSABRE engine, rebuilt ​

Layout and routing were rebuilt around a shared LightSABRE engine: bidirectional SABRE layout search with a balanced layout-routing objective, VF2 completion for embeddable subgraphs, ring-neighborhood candidates, and a work-budget circuit breaker for pathological instances. The Python API is unchanged — same TranspilationOptions, same RoutingMethod.LIGHT_SABRE — but the results moved:

  • on the benchpress suite, 1,044 paired cases against Qiskit 2.5.2 (both sides passing all 1,044), pyqpanda3 0.5.0 is faster on 1,040 of 1,044 cases with a geometric-mean time ratio of 0.233x;
  • output quality holds up: total two-qubit gate count is 0.957x of Qiskit's across the suite, and two-qubit depth 0.918x on the 892 abstract-compilation cases;
  • only 6 pyqpanda3 compile cases exceed 10 seconds, against 28 for the reference.

Determinism is explicit: fix TranspilationExecutionConfig.seed and repeated transpilations of the same program are identical, and the config now also slots in as the last positional argument of transpile().

Measured against 0.4.1 ​

To quantify the release, the two wheels were run side by side on the same host and interpreter (CPython 3.14), on identical seeded workloads: a random long-range 19-qubit circuit transpiled against three fixed coupling maps (a linear chain, a 5x5 grid, and the same heavy-hex patch generator benchpress uses), a QV100-scale OpenQASM import, 5-controlled-X synthesis into the CZ basis, and a random 4-qubit unitary-oracle loop. The reported times are the minimum of five interleaved rounds, so scheduling noise from the shared machine only ever works against the faster side.

pyqpanda3 0.5.0 vs 0.4.1 paired workloads

The kernel-level changes show up directly: the QASM import fast path more than doubled its throughput (0.43x of the 0.4.1 time), MCX synthesis runs at 0.44x while emitting the identical 248-CZ output, and transpiling the long-range circuit onto a linear chain takes 0.77x of the time. On the square and heavy-hex coupling maps the wall time is on par with 0.4.1 -- but the transpiled circuits are strictly better: with the seed fixed, the output two-qubit gate count drops by 15% (linear), 15% (square), and 11% (heavy-hex). That is the LightSABRE rewrite doing its job: comparable or better compile times, measurably better circuits.

For state-vector simulation, float64 evolution stays performance-comparable with 0.4.1; the speedup lives in the new float32 path (roughly half the state memory, and the SIMD kernels it feeds) -- on the release benchmark suite the AVX2 kernels deliver a median 2.13x over scalar there.

A native RESET ​

RESET(qubit) collapses a qubit to |0> as a first-class operation — the measure-plus-conditional-X dance is over:

python
from pyqpanda3.core import CPUQVM, QProg, RESET, X, measure

prog = QProg() << X(0) << RESET(0) << measure(0, 0)
qvm = CPUQVM()
qvm.run(prog, 100)
print(qvm.result().get_counts())

Output:

text
{'0': 100}

It round-trips through OriginIR and parses from OpenQASM reset statements. (Exporting RESET back to OpenQASM is not supported in 0.5.0 — use OriginIR for serialization.)

Also worth noting ​

  • MPS truncation dials: MPSQVM(truncation_atol=..., max_bond_dimension=...) plus stats() reporting discarded_weight and the largest bond dimension actually seen — you can now trade accuracy for memory on purpose, and prove what it cost.
  • Noise semantics stated explicitly: row-major Kraus matrices, ReadoutError indexed M[true][measured], noise applied after its instruction, local errors overriding defaults — validated by a suite run against both libraries. NoiseModel also gained proper copy()/deepcopy().
  • Cloud ergonomics: QCloudOptions.get_custom_option() now returns the stored value's actual type — the previous per-type overloads collapsed to a single Python signature, leaving only int reachable.
  • Faster kernels elsewhere: the ORACLE gate path uses zero-copy matrices and hand-written AVX2 GEMM; QASM import of QV100-scale files drops from 1.48s to 0.73s
  • mimalloc in the wheel: wheels are now platform-specific and bundle the mimalloc allocator for steadier large-state performance.

See the v0.5.0 release notes for the full list of API changes, and the benchmark page for the complete benchpress analysis.

Released under the MIT License.