pyqpanda3 v0.4.0 connected the hardware workflow end to end: cloud chip metadata, fidelity-aware layout, instruction export, and schedule visualization. v0.4.1 targets a layer closer to the "kernel" of that pipeline — now that the backend can give you fidelity data, does the transpiler actually use it to make decisions?
The answer is: yes, now it does. The theme of this release is letting fidelity data truly drive transpilation decisions, and filling two long-standing simulator gaps.
Strongly-typed transpilation options and metrics
In 0.4.0, transpile() used positional arguments to control the optimization level and layout strategy. 0.4.1 adds TranspilationOptions, which groups every tunable into a single object, and pairs it with TranspilationResult to return the transpiled program, compile time, and metrics in one call.
from pyqpanda3.core import QProg, H, CZ, measure
from pyqpanda3.transpilation import (
ChipBackend,
LayoutMethod,
RoutingMethod,
Transpiler,
TranspilationOptions,
)
prog = QProg()
prog << H(0) << CZ(0, 1) << measure(0, 0) << measure(1, 1)
backend = ChipBackend()
backend.chip_topology_edges = [[0, 1], [1, 2]]
backend.basic_gates = ["RPhi", "CZ"]
backend.single_gate_fidelity = {0: 0.99, 1: 0.99, 2: 0.99}
backend.readout_fidelity = {0: 0.90, 1: 0.90, 2: 0.99}
backend.double_gate_fidelity = {(0, 1): 0.90, (1, 2): 0.99}
options = TranspilationOptions()
options.optimization_level = 2
options.layout_method = LayoutMethod.FIDELITY
options.routing_method = RoutingMethod.LIGHT_SABRE
options.approximation_degree = 0.99
options.execution.routing_trials = 8
options.execution.seed = 42
result = Transpiler().transpile(prog, backend, options)
print(type(result).__name__)
print(result.compile_time_ns > 0)
print(sorted(result.metrics.keys()))Example output shape:
TranspilationResult
True
['depth', 'fidelity_exact', 'fidelity_fallback', 'fidelity_unknown', 'log_expected_fidelity', 'two_qubit_gate_count']result.metrics has the same shape as the dict returned by the standalone function transpilation_metrics(prog, backend) — it includes the two-qubit gate count, depth, log-expected fidelity, and counts of gates whose fidelity was matched exactly / fell back / was unknown. Transpilation quality is finally quantifiable and comparable, not just "it ran."
LightSABRE routing
RoutingMethod.LIGHT_SABRE (the string "light_sabre") enables the new layered-lookahead router. It expands the interaction graph layer by layer while routing, reducing two-qubit gate insertion and swap depth — useful for backends with limited connectivity or deeper circuits.
from pyqpanda3.core import QProg, H, CZ, measure
from pyqpanda3.transpilation import ChipBackend, Transpiler
prog = QProg()
prog << H(0) << CZ(0, 1) << measure(0, 0) << measure(1, 1)
backend = ChipBackend()
backend.chip_topology_edges = [[0, 1], [1, 2]]
backend.basic_gates = ["RPhi", "CZ"]
transpiled = Transpiler().transpile(
prog,
backend,
{},
2,
"fidelity",
"light_sabre",
"default",
approximation_degree=0.99,
)
print(type(transpiled).__name__)
print(len(transpiled.operations()) > 0)Output:
QProg
TrueHeisenberg-picture expectation simulator
In many cases you don't need the full state vector — you only want the expectation value <H> of some Pauli Hamiltonian. SparseHeisenbergSimulator computes exactly that, in the Heisenberg picture, without sampling and without returning a state vector. For the "supported gate set + product initial state" class of problems it is far cheaper than full simulation.
from pyqpanda3.core import QProg, CZ, SparseHeisenbergSimulator
zero = (1 + 0j, 0 + 0j) # (|0> amplitude, |1> amplitude) for each qubit
prog = QProg() << CZ(0, 1) # |00> unchanged
sim = SparseHeisenbergSimulator(num_qubits=2)
sim.set_initial_product_state([zero, zero])
sim.add_observable("ZZ", 1.0)
sim.evolve_backward(prog)
print(round(sim.expectation(), 6))Output:
1.0<ZZ> on |00> is 1.0, matching the analytic value. When the number of operator terms grows, you can truncate with max_terms and truncation_norm="l1", and inspect what was dropped via stats().
Matrix-product-state (MPS) simulator
MPSQVM runs quantum programs on a matrix-product-state backend, keeping memory usage low for weakly-entangled circuits even at large qubit counts.
from pyqpanda3.core import H, MPSQVM, QProg, measure
prog = QProg() << H(0) << measure(0, 0) << measure(1, 1)
qvm = MPSQVM()
qvm.run(prog, 1000)
result = qvm.result()
print(type(result).__name__)
print(sum(result.get_counts().values()) == 1000)Example output shape:
QResult
TrueAlso worth noting
state_fidelity()lands inquantum_info, supporting all four density-matrix / state-vector combinations — filling the gap of "directly comparing two quantum states."Choi,SuperOp, andPTMall gainndarray(), making them easy to bridge with the NumPy ecosystem.QProg.get_measure_qubits_cbits()returns measurement pairs in program order, handy for verifying the measurement mapping before submitting to the cloud.- A batch of stability fixes: the
VQCircuit.append(vqc)self-reference segfault, the VF2 partial-layout segfault, theParamExpressionempty-expression crash, dephasing-noise Kraus orientation, theget_matrixmemory leak, andPartialAmplitudeQVMindex bounds checking.
The second half of 0.4.1: initial states, Heisenberg, and task cancellation
The 0.4.1 features landed in two waves during the release cycle: the first half brought the fidelity-driven transpilation pipeline and the two simulators; the second half added initial-state control for the simulators, truncation statistics for the Heisenberg picture, and cloud task cancellation.
Initial state: CPUQVM.init_state
CPUQVM gains init_state(state) in 0.4.1: set the simulator to any initial state before running, or call it without arguments to restore the default |0...0>. For example, put both qubits in a superposition before measuring:
import numpy as np
from pyqpanda3.core import CPUQVM, QProg, measure
qvm = CPUQVM()
qvm.init_state([1 / np.sqrt(2), 0, 0, 1 / np.sqrt(2)])
prog = QProg() << measure([0, 1], [0, 1])
qvm.run(prog, 1000)
print(qvm.result().get_counts())
qvm.init_state() # restore default |0...0>
qvm.run(prog, 1000)
print(qvm.result().get_counts())Example output shape:
{'00': 524, '11': 476}
{'00': 1000}Note: MPSQVM's C++ init_state is not exposed in the Python bindings (0.4.1); use CPUQVM.init_state on the Python side.
Truncation statistics in the Heisenberg picture
Earlier we mentioned that SparseHeisenbergSimulator can truncate with max_terms and truncation_norm. What does that look like in practice? RX rotations inflate the number of operator terms from 1 to 8; with max_terms=4 the surplus terms are dropped by L1 norm, and stats() reports what was discarded:
from pyqpanda3.core import QProg, H, CNOT, RX, SparseHeisenbergSimulator
zero = (1 + 0j, 0 + 0j)
prog = QProg() << H(0) << CNOT(0, 1) << RX(0, 0.7) << RX(1, 1.3) << RX(2, 2.1)
# No truncation: watch the term count inflate
full = SparseHeisenbergSimulator(num_qubits=3)
full.set_initial_product_state([zero, zero, zero])
full.add_observable("ZZZ", 1.0)
full.evolve_backward(prog)
print("no truncation terms:", full.stats().active_terms)
# Truncation: keep at most 4 terms, drop the rest by L1 norm
trunc = SparseHeisenbergSimulator(num_qubits=3, max_terms=4, truncation_norm="l1")
trunc.set_initial_product_state([zero, zero, zero])
trunc.add_observable("ZZZ", 1.0)
trunc.evolve_backward(prog)
s = trunc.stats()
print("expectation:", round(trunc.expectation(), 6))
print("active_terms:", s.active_terms)
print("truncated_terms:", s.truncated_terms)
print("truncated_l1:", round(s.truncated_l1, 6))
print("coefficient_l1:", round(s.coefficient_l1, 6))Output:
no truncation terms: 8
expectation: 0.400378
active_terms: 4
truncated_terms: 4
truncated_l1: 0.51565
coefficient_l1: 2.373074Cloud task cancellation
QCloudJob gains cancel() and the static method cancel_task(task_id), so you can actively cancel a submitted cloud task; True means the cloud service accepted the cancellation request. Cancelling requires cloud credentials and a real task, so it cannot be executed locally:
from pyqpanda3.qcloud import QCloudJob, QCloudService
# Requires cloud credentials and a real task; cannot run locally
service = QCloudService("your_api_key")
job = QCloudJob("your_job_id")
print(job.cancel()) # cancel the current job
print(QCloudJob.cancel_task("your_task_id")) # cancel by task ID (static)See the v0.4.1 release notes for the full list of API changes.