0.5.0
pyqpanda3 0.5.0 is the performance release. The headline changes are precision-selectable simulation (every CPU-family simulator now accepts precision="float32" or "float64"), a portable SIMD dispatcher that accelerates the state-vector kernels at runtime, and a rewritten LightSABRE layout/routing engine that makes transpilation both faster and measurably better in output quality. Alongside these, 0.5.0 adds a native RESET operation, a simulator capability registry, MPS truncation controls with run statistics, explicitly stated noise-model conventions, and a bundled mimalloc allocator shipped inside platform-specific wheels.
1. Simulator precision selection
All CPU-family simulators now take a precision keyword at construction. float32 halves the memory footprint of the state and roughly doubles kernel throughput on SIMD-capable hosts; float64 remains the default for full-accuracy work. The choice is immutable per instance and readable back through the precision property, together with supported_precisions.
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)
prog = QProg() << H(0) << CNOT(0, 1)
qvm32 = CPUQVM(precision="float32")
qvm32.init_state(seed)
state32 = qvm32.current_state()
print(state32.dtype, qvm32.precision, tuple(qvm32.supported_precisions))
qvm64 = CPUQVM() # default: float64
qvm64.init_state(seed)
print(qvm64.current_state().dtype, qvm64.precision)
mps = MPSQVM(precision="float32")
mps.run(prog, 2)
print(mps.current_state().dtype, mps.current_state().shape)Output:
complex64 float32 ('float32', 'float64')
complex128 float64
complex64 (4,)current_state() is new on CPUQVM and MPSQVM: it returns the state as a NumPy array in the simulator's native precision (complex64 for float32, complex128 for float64). Note the two classes report different things — CPUQVM.current_state() mirrors the state installed by init_state(...) and is not populated by run() (read a run's state through result().get_state_vector()), whereas MPSQVM.current_state() is populated by run(). DensityMatrixSimulator gains the matching density_matrix_native().
The precision family covers CPUQVM, DensityMatrixSimulator, MPSQVM, PartialAmplitudeQVM, SingleAmplitudeQVM, and SparseHeisenbergSimulator (float32/float64 each). Stabilizer reports the exact precision="exact" and rejects arithmetic precisions. GPUQVM supports both float32 and float64 when the build is CUDA-enabled; a build without CUDA registers float64 only and marks the backend unavailable. Note that the registry's available flag describes how the wheel was built, not whether the host can actually run it: a CUDA-enabled build on a host whose driver predates the bundled CUDA runtime reports available=True and raises the CUDA runtime's own RuntimeError at construction.
Invalid requests fail loudly and early:
from pyqpanda3.core import CPUQVM, Stabilizer
try:
CPUQVM(precision="float16")
except ValueError as e: # InvalidPrecisionError, a ValueError subclass
print(type(e).__name__, "-", e)
try:
Stabilizer(precision="float32")
except ValueError as e: # UnsupportedPrecisionError
print(type(e).__name__, "-", e)Output:
InvalidPrecisionError - unknown precision 'float16'
UnsupportedPrecisionError - backend 'Stabilizer' does not support precision 'float32'; supported precisions are: exactNew exception types: InvalidPrecisionError, UnsupportedPrecisionError, PrecisionConversionError (all ValueError subclasses), plus BackendUnavailableError (RuntimeError subclass) raised when constructing a simulator whose build is absent.
2. Simulator capability registry
simulator_capabilities() answers "what can run here" without constructing anything. Called with a name it returns one SimulatorCapabilities record; called without arguments it returns all of them.
from pyqpanda3.core import simulator_capabilities
for cap in simulator_capabilities():
print(f"{cap.name:26s} {','.join(cap.supported_precisions):16s}"
f" default={cap.default_precision:8s} available={str(cap.available):5s}"
f" domain={cap.simd_domain}")Output:
CPUQVM float32,float64 default=float64 available=True domain=StateVector
DensityMatrixSimulator float32,float64 default=float64 available=True domain=Density
GPUQVM float64 default=float64 available=False domain=NotApplicable
MPSQVM float32,float64 default=float64 available=True domain=Tensor
PartialAmplitudeQVM float32,float64 default=float64 available=True domain=Amplitude
SingleAmplitudeQVM float32,float64 default=float64 available=True domain=Amplitude
SparseHeisenbergSimulator float32,float64 default=float64 available=True domain=Sparse
Stabilizer exact default=exact available=True domain=NotApplicableEach record exposes name, supported_precisions, default_precision, available, and simd_domain (one of StateVector, Density, Tensor, Amplitude, Sparse, NotApplicable). The registry is host-dependent: on a build without CUDA the GPUQVM row reads available=False and constructing a GPUQVM raises BackendUnavailableError instead of failing obscurely at import time; a CUDA-enabled build reports float32,float64 there instead.
3. SIMD runtime dispatch
The state-vector kernels are now compiled for multiple SIMD ISAs and selected at process start by a runtime detector — x86_64 hosts pick between scalar, sse2, avx2, and avx512; ARM64 hosts between scalar, neon, sve, and sve2. The new pyqpanda3.core.simd submodule exposes the diagnostics and the preference override:
from pyqpanda3.core import simd
cap = simd.capabilities()
print(cap.architecture, list(cap.available_backends), cap.active_backend)
simd.set_preference("scalar") # force scalar kernels
print(simd.active_backend())
simd.set_preference("auto") # restore the detected default
print(simd.active_backend())Output (on an AVX2-capable x86_64 host):
x86_64 ['scalar', 'sse2', 'avx2', 'avx512'] avx2
scalar
avx2simd.capabilities() returns an immutable snapshot with architecture, compiled_backends, available_backends, default_backend, active_backend, and fallback_reason. simd.set_preference(backend, strict=False) never raises for an unavailable backend (it records a fallback reason instead); with strict=True it raises UnsupportedSIMDBackendError. On the release benchmark host the AVX2 kernels deliver a median 2.13x speedup over scalar across the representative random-circuit suite (~2.5x at float32, ~1.9x at float64).
4. MPS truncation control and run statistics
MPSQVM now accepts truncation_atol and max_bond_dimension at construction, and reports what the SVD truncation actually did through the new stats() method (an MPSRunStats with discarded_weight and max_bond_dimension_seen).
import numpy as np
from pyqpanda3.core import MPSQVM, QProg, RY, CZ
rng = np.random.default_rng(7)
prog = QProg()
for q in range(6):
prog << RY(q, rng.uniform(0, np.pi))
for layer in range(4):
pairs = [(0, 1), (2, 3), (4, 5)] if layer % 2 == 0 else [(1, 2), (3, 4)]
for a, b in pairs:
prog << CZ(a, b) << RY(a, rng.uniform(0, np.pi)) << RY(b, rng.uniform(0, np.pi))
capped = MPSQVM(precision="float64", max_bond_dimension=2)
capped.run(prog, 1)
print(capped.stats().discarded_weight, capped.stats().max_bond_dimension_seen)
exact = MPSQVM()
exact.run(prog, 1)
print(exact.stats().discarded_weight, exact.stats().max_bond_dimension_seen)Output:
0.024353811677256556 2
1.8613363972962274e-34 4The default truncation_atol is precision-dependent (1e-6 for float32, 1e-12 for float64); max_bond_dimension is unlimited by default. SingleAmplitudeQVM gains a matching stats() (a SingleAmplitudeStats with cache_hit_count, cache_miss_count, and peak_workspace_bytes).
5. Native RESET operation
RESET(qubit) collapses a qubit to |0> as a first-class, non-unitary operation — no more measure + conditional-X emulation in user code. It is supported exactly on the CPU state-vector and density-matrix simulators; other backends raise at execution time, and controlled RESET has no physical definition.
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:
{'0': 100}The operation round-trips through OriginIR in both directions and is parsed from OpenQASM 2.0 reset q[i]; statements into the native gate. Note one boundary: exporting a program that contains RESET back to OpenQASM with convert_qprog_to_qasm() raises a RuntimeError in 0.5.0 — serialize through OriginIR when RESET is present.
6. LightSABRE transpiler engine rewrite
The layout and routing stack was rebuilt around a shared LightSABRE engine: a bidirectional SABRE layout search with a balanced layout-routing objective, VF2-matching completion for subgraph-embeddable circuits, ring-neighborhood layout candidates, and a work-budget circuit breaker that bounds the search on pathological instances. The Python entry points are unchanged — RoutingMethod.LIGHT_SABRE, LayoutMethod, and the TranspilationOptions.execution config from 0.4.1 — but the outputs are better and the compile times are substantially lower.
On the benchpress suite (1,044 paired test cases against Qiskit 2.5.2, both sides passing 1,044/1,044), pyqpanda3 0.5.0 is faster than Qiskit on 1,040 of 1,044 cases with a geometric-mean time ratio of 0.233x, while the total two-qubit gate count of the outputs stays slightly ahead (0.957x). Across the 892 abstract-compilation cases the geometric mean is 0.212x and the two-qubit depth total is 0.918x of Qiskit's. See the benchmark page for the full analysis.
Determinism is part of the contract: fixing TranspilationExecutionConfig.seed makes repeated transpilations of the same program produce identical output, and the config can now also be passed as the last positional argument of the positional-argument transpile() overload:
from pyqpanda3.core import CNOT, H, QProg
from pyqpanda3.transpilation import Transpiler, TranspilationExecutionConfig, generate_topology
prog = QProg() << H(0) << CNOT(0, 1)
topo = generate_topology(4, "square")
config = TranspilationExecutionConfig()
config.seed = 42
transpiler = Transpiler()
a = transpiler.transpile(prog, topo, {}, 2, ["RX", "RY", "RZ", "CZ"], config)
b = transpiler.transpile(prog, topo, {}, 2, ["RX", "RY", "RZ", "CZ"], config)
print(len(a.operations()) == len(b.operations()))Output:
True7. Noise model semantics stated explicitly
Noise-model conventions are now stated explicitly rather than left to inference: Kraus operator matrices are row-major, ReadoutError matrices are indexed M[true][measured], quantum errors apply after the instruction they are attached to, and local (per-qubit) errors override default errors rather than compose with them. NoiseModel also becomes properly copyable:
import copy
from pyqpanda3.core import NoiseModel
model = NoiseModel()
model.add_read_out_error([[0.9, 0.1], [0.2, 0.8]], 0)
duplicate = copy.deepcopy(model)
snapshot = model.copy()
print(duplicate is not model, snapshot is not model)Output:
True True8. Faster kernels, smaller wheels
The 0.4.1 and 0.5.0 wheels were also measured head-to-head on the same host and interpreter, on identical seeded workloads (random long-range 19-qubit circuit against three fixed coupling maps, QV100-scale QASM import, 5-controlled-X synthesis, a 4-qubit unitary-oracle loop; times are the minimum of five interleaved rounds). The kernel-level wins are direct: QASM import at 0.43x of the 0.4.1 time, MCX synthesis at 0.44x with the identical 248-CZ output, linear-topology transpilation at 0.77x; square- and heavy-hex-topology transpilation times are on par while the transpiled two-qubit gate counts drop by 15% / 15% / 11% (linear / square / heavy-hex) under a fixed seed. State-vector float64 evolution stays performance-comparable with 0.4.1 -- the simulation speedup comes from the new float32 path and the SIMD kernels (a median 2.13x AVX2-over-scalar on the release benchmark suite).

- Oracle kernel: the ORACLE (arbitrary large-matrix) gate simulation path uses zero-copy matrix handling, blocked GEMM, and hand-written AVX2 complex GEMV/GEMM kernels; qubit-order semantics for routed circuits were corrected (decompositions are now equivalent after layout remapping).
- QASM import fast path: importing QV100-scale QASM files no longer materializes intermediate circuit copies; the QV100 import benchmark drops from 1.48s to 0.73s.
- DAG and translation: DAG circuit construction gained move semantics and a per-node scan-head cache (end-to-end transpilation about 20% faster on the paired suite), the constant-folding optimizer converges by fixed-point threshold, and cancellation/star-sweep passes deallocate deterministically.
- mimalloc: wheels bundle the mimalloc allocator as a private shared runtime and the Python extension allocates through it, giving more stable large-state performance; wheels are now platform-specific (
cpXY-cpXY-<platform>tags), sopipresolves the right binary per interpreter and OS.
API changes
- Added
precisionconstructor keyword andprecision/supported_precisionsproperties toCPUQVM,DensityMatrixSimulator,MPSQVM,PartialAmplitudeQVM,SingleAmplitudeQVM,SparseHeisenbergSimulator, andGPUQVM;Stabilizerreportsprecision="exact". - Added
pyqpanda3.core.simulator_capabilities(name=None)and theSimulatorCapabilitiesrecord type (name,supported_precisions,default_precision,available,simd_domain). - Added the
pyqpanda3.core.simdsubmodule:simd.capabilities(),simd.active_backend(),simd.set_preference(backend, strict=False), and theSIMDCapabilitiessnapshot type. - Added exceptions
InvalidPrecisionError,UnsupportedPrecisionError,PrecisionConversionError(subclasses ofValueError) andBackendUnavailableError,UnsupportedSIMDBackendError(subclasses ofRuntimeError). - Added
CPUQVM.current_state()andMPSQVM.current_state()returning native-precisionnumpy.ndarraystate. - Added
DensityMatrixSimulator.density_matrix_native()returning the density matrix in the simulator's native dtype. - Added
MPSQVMconstructor keywordstruncation_atolandmax_bond_dimension, andMPSQVM.stats() -> MPSRunStats(discarded_weight,max_bond_dimension_seen). - Added
SingleAmplitudeQVM.stats() -> SingleAmplitudeStats(cache_hit_count,cache_miss_count,peak_workspace_bytes). - Added the native
RESET(qubit)gate and theGateType.RESETenum value; OriginIR emits and parses it, QASM parses it; QASM export of RESET raisesRuntimeError(known limitation). - Added the
check_unitarykeyword toOracle(qubits, matrix, check_unitary=True), which validates the matrix before the gate is built and raisesValueErrorotherwise. The Oracle matrix is applied in the order the qubits are given, withqubits[0]as the most significant factor. - Added
NoiseModel.copy(),NoiseModel.__copy__,NoiseModel.__deepcopy__. expval_pauli_operator()/expval_hamiltonian()acceptinit_state(a state vector) to skip re-preparation.Transpiler.transpile()positional-argument overload now acceptsexecution_configas its last positional argument (in addition to keyword form).- Fixed
QCloudOptions.get_custom_option(): the four per-type overloads collapsed to a single Python signature so only theintinstantiation was reachable; it now returns the stored value's actual type.int,floatandstrround-trip unchanged; a Pythonboolcomes back asint(True->1), becauseboolis anintsubclass and the binding resolves it to the integer overload. PartialAmplitudeQVM.get_state_vector()now returns NumPy arrays (dtype follows the simulator precision).QCloudBackend.chip_info()no longer raisesTypeErrorwhen options are omitted.StabilizerResult.get_state_vector()now raises a descriptiveRuntimeErrorinstead of silently returning an empty list.convert_qasm_file_to_qprog()raisesRuntimeErroron missing files instead of returning an empty program.direct_twirl(input_circ, twirled_gate, seed)honors seeds below 8000 (previously collapsed to the same result).- Noise-model semantics stated explicitly: Kraus row-major,
ReadoutErrorindexedM[true][measured], noise applied after the instruction, local overrides default. - QASM export of multi-controlled gates uses compact identities (e.g. Toffoli 59 → 1 line, 3-controlled Z 150 → 33 lines) and correct CSD-based decomposition of exported operators.
QProg.remapdefaultremap_cbits={}no longer drops classical-bit mappings.- Wheels are platform-specific (
cpXY-cpXY-<platform>tags) and bundle the mimalloc runtime;pyqpanda3._native_runtimeloads the bundled DLL directory on Windows.