Dynamic Circuits
Dynamic circuits enable classical control flow within quantum programs. pyqpanda3 supports mid-circuit measurement and conditional execution through qif, qelseif, qelse, and qwhile constructs.
What Are Dynamic Circuits?
Traditional quantum circuits are static sequences of gates followed by measurement. Dynamic circuits introduce classical control flow:
- Conditional execution: Apply gates only if a measurement condition is met
- Loops: Repeat operations based on measurement outcomes
- Mid-circuit measurement: Measure qubits during execution, not just at the end
qif - Conditional Branching
The qif construct implements classical if-then-else logic based on classical bit values.
Basic If-Then
Use qif when you need to apply gates conditionally on measurement results:
from pyqpanda3 import core
# Create a program with conditional execution
prog = core.QProg()
prog << core.X(0) # Prepare |1⟩ on qubit 0
prog << core.I(1) # Qubit 1 in |0⟩
# Define the if-branch: apply X(1) if cbit[1] is 1
if_branch = core.QProg()
if_branch << core.X(1)
# Add conditional branch
prog << core.qif([1]).then(if_branch).qendif()
print(prog)The condition qif([1]) checks if classical bit 1 is set to 1.
If-Then-Else
Add an else branch for the case when the condition is not met:
prog = core.QProg()
prog << core.X(0) << core.I(1)
# Define both branches
if_branch = core.QProg()
if_branch << core.X(1)
else_branch = core.QProg()
else_branch << core.X(0)
# Add conditional with both branches
prog << core.qif([1]).then(if_branch).qelse(else_branch)
print(prog)Using qelseif for Multi-Condition Branching
Chain multiple conditions using qelseif:
prog = core.QProg()
prog << core.X(0) << core.I(1) << core.I(2) << core.I(3)
# Define multiple branches
branch_1 = core.QProg()
branch_1 << core.X(1)
branch_2 = core.QProg()
branch_2 << core.X(0)
branch_3 = core.QProg()
branch_3 << core.X(2)
branch_4 = core.QProg()
branch_4 << core.CNOT(0, 1)
# Build multi-branch conditional
(prog << core.qif([1]).then(branch_1)
.qelseif([0, 3]).then(branch_2)
.qelseif([2]).then(branch_3)
.qelse(branch_4))
print(prog)The flow is:
Condition Semantics
The condition qif([c0, c1, c2]) checks if ALL specified classical bits are 1 (logical AND):
qif([0]): True if cbit 0 == 1qif([0, 1]): True if cbit 0 == 1 AND cbit 1 == 1qif([0, 1, 2]): True if cbit 0 == 1 AND cbit 1 == 1 AND cbit 2 == 1
qwhile - Conditional Loops
The qwhile construct repeats a loop body as long as a classical condition is met.
Basic While Loop
prog = core.QProg()
# Prepare initial state and measure
prog << core.X(0)
prog << core.measure(0, 0)
# Define the loop body
loop_body = core.QProg()
loop_body << core.X(1) # Flip qubit 1
loop_body << core.X(0) # Flip qubit 0 back
loop_body << core.measure(0, 0) # Re-measure qubit 0
# Add while loop: continue while cbit[0] is 1
prog << core.qwhile([0]).loop(loop_body)
print(prog)The loop executes as:
Practical Examples
Example 1: Repeat-Until-Success
Use dynamic circuits to repeat an operation until a desired measurement outcome:
from pyqpanda3 import core
# Repeat H-measure until we get |1⟩
prog = core.QProg()
# Initial measurement
prog << core.H(0)
prog << core.measure(0, 0)
# Loop body: keep trying until we measure 1
loop_body = core.QProg()
loop_body << core.H(0)
loop_body << core.measure(0, 0)
# While cbit[0] == 0 (didn't get 1), try again
# Note: qwhile continues while condition is True
# We need to loop while cbit[0] is 0
prog << core.qwhile([0]).loop(loop_body)
print("Repeat-until-success circuit:")
print(prog)Example 2: Mid-Circuit Measurement with Conditional Gate
Perform a measurement and conditionally apply a correction:
from pyqpanda3 import core
# Create a 3-qubit entangled state
prog = core.QProg()
prog << core.H(0)
prog << core.CNOT(0, 1)
prog << core.CNOT(1, 2)
# Mid-circuit measurement of qubit 0
prog << core.measure(0, 0)
# Conditionally apply correction on qubits 1 and 2
correction = core.QProg()
correction << core.Z(1)
correction << core.Z(2)
# If measurement was 1, apply correction
prog << core.qif([0]).then(correction).qendif()
# Final measurement
prog << core.measure([1, 2], [1, 2])
print("Mid-circuit measurement with correction:")
print(prog)
# Run the circuit
machine = core.CPUQVM()
machine.run(prog, shots=1000)
print(machine.result().get_counts())Example 3: Error Detection with Parity Check
Use dynamic circuits for error detection using parity measurements:
from pyqpanda3 import core
# Encode logical |0⟩ in 3-qubit repetition code
prog = core.QProg()
prog << core.H(0) # Superposition
prog << core.CNOT(0, 1) # Entangle
prog << core.CNOT(0, 2) # Entangle
# Parity check measurements (into ancilla cbits)
prog << core.CNOT(0, 3) # Parity q0-q1 (using ancilla q3)
prog << core.CNOT(1, 3)
prog << core.measure(3, 0) # Parity into cbit 0
# Conditional correction based on parity
correct_01 = core.QProg()
correct_01 << core.X(1) # Flip qubit 1 back
correct_10 = core.QProg()
correct_10 << core.X(0) # Flip qubit 0 back
correct_11 = core.QProg()
correct_11 << core.X(2) # Flip qubit 2 back
# Note: This is a simplified example
# Real error correction would use syndrome decoding
print("Error detection circuit:")
print(prog)Example 4: Adaptive Phase Estimation
Use dynamic circuits for iterative phase estimation:
from pyqpanda3 import core
import numpy as np
# Iterative phase estimation for a unitary U with eigenvalue e^(iφ)
# We want to estimate φ
phi = np.pi / 4 # True phase
prog = core.QProg()
# Prepare eigenstate |1⟩ of RZ
prog << core.X(1)
# k-th iteration (simplified single-step example)
prog << core.H(0)
prog << core.RZ(1, phi) # Apply unitary
prog << core.H(0)
prog << core.measure(0, 0)
# Conditional phase correction based on measurement
correction = core.QProg()
correction << core.RZ(1, -np.pi / 2)
prog << core.qif([0]).then(correction).qendif()
# Final measurement
prog << core.measure(1, 1)
print("Adaptive phase estimation circuit:")
print(prog)
machine = core.CPUQVM()
machine.run(prog, shots=1000)
print(machine.result().get_counts())Teleportation Protocol
Quantum teleportation transmits an unknown quantum state from one qubit to another using entanglement and classical communication. Dynamic circuits are essential because the correction operations depend on mid-circuit measurement results.
The teleportation protocol works as follows:
- Alice and Bob share an entangled Bell pair
- Alice performs a Bell measurement on her qubit and the unknown state
- Alice sends the two classical bits to Bob
- Bob applies corrections based on the measurement outcomes
from pyqpanda3 import core
# Quantum teleportation of an unknown state |psi⟩ on qubit 0
# Qubit allocation:
# q0: the unknown state |psi⟩ to teleport
# q1: Alice's half of the Bell pair
# q2: Bob's half of the Bell pair
prog = core.QProg()
# Step 1: Prepare the unknown state |psi⟩ = alpha|0⟩ + beta|1⟩
# For this example, we prepare |psi⟩ = Ry(theta)|0⟩
theta = 0.7 # Arbitrary rotation angle
prog << core.RY(0, theta)
# Step 2: Create Bell pair between q1 and q2
# |00⟩ -> (H on q1) -> (|0⟩+|1⟩)|0⟩/sqrt(2) -> (CNOT q1,q2) -> |Φ+⟩
prog << core.H(1)
prog << core.CNOT(1, 2)
# Step 3: Bell measurement on (q0, q1)
# First apply the inverse Bell basis transformation
prog << core.CNOT(0, 1)
prog << core.H(0)
# Step 4: Measure q0 and q1 into classical bits
prog << core.measure(0, 0) # cbit 0 holds measurement of q0
prog << core.measure(1, 1) # cbit 1 holds measurement of q1
# Step 5: Bob's conditional corrections on q2
# If cbit[0] == 1, apply Z correction
z_correction = core.QProg()
z_correction << core.Z(2)
no_z_correction = core.QProg()
prog << core.qif([0]).then(z_correction).qelse(no_z_correction)
# If cbit[1] == 1, apply X correction
x_correction = core.QProg()
x_correction << core.X(2)
no_x_correction = core.QProg()
prog << core.qif([1]).then(x_correction).qelse(no_x_correction)
# Step 6: Final measurement of the teleported state on q2
prog << core.measure(2, 2)
print("Quantum Teleportation Circuit:")
print(prog)
# Run and verify
machine = core.CPUQVM()
machine.run(prog, shots=1000)
counts = machine.result().get_counts()
print("Teleportation results:", counts)The correction rules are:
| Measurement (q0, q1) | Correction on q2 |
|---|---|
| (0, 0) | None |
| (0, 1) | |
| (1, 0) | |
| (1, 1) |
After correction, qubit 2 holds the original state
Quantum Error Correction Example
The 3-qubit repetition code protects a single logical qubit against bit-flip (
The syndrome extraction uses two ancilla qubits to detect which data qubit (if any) experienced a bit-flip. The syndrome
from pyqpanda3 import core
# 3-qubit repetition code for bit-flip error correction
# Qubit layout:
# q0, q1, q2: data qubits (encoded logical qubit)
# q3: ancilla for parity check q0 XOR q1
# q4: ancilla for parity check q1 XOR q2
prog = core.QProg()
# Step 1: Encode |psi⟩ into the 3-qubit code
# Start with |psi⟩ on q0 (e.g., a superposition)
prog << core.RY(0, 1.2) # Prepare an arbitrary state on q0
# Entangle: |psi⟩|00⟩ -> (alpha|000⟩ + beta|111⟩)
prog << core.CNOT(0, 1)
prog << core.CNOT(0, 2)
# Step 2: Simulate a bit-flip error on qubit 1
# (In practice this would be unplanned noise)
prog << core.X(1)
# Step 3: Syndrome extraction using ancilla qubits
# Ancilla q3 measures parity of (q0, q1)
prog << core.CNOT(0, 3)
prog << core.CNOT(1, 3)
# Ancilla q4 measures parity of (q1, q2)
prog << core.CNOT(1, 4)
prog << core.CNOT(2, 4)
# Measure ancillas into classical bits
prog << core.measure(3, 0) # s1 -> cbit 0
prog << core.measure(4, 1) # s2 -> cbit 1
# Step 4: Conditional correction based on syndrome
# Syndrome table:
# (0,0) -> no error (or error on no qubit)
# (1,0) -> error on q0
# (1,1) -> error on q1
# (0,1) -> error on q2
# Correction for q0: apply X(0) when s1=1, s2=0
# We approximate this by checking both syndrome bits
correct_q0 = core.QProg()
correct_q0 << core.X(0)
correct_q1 = core.QProg()
correct_q1 << core.X(1)
correct_q2 = core.QProg()
correct_q2 << core.X(2)
no_correction = core.QProg()
# Use conditional branches for each syndrome pattern
# Since qif checks ALL bits == 1, we use the pattern matching
# For (1,0) syndrome: only cbit[0] is 1
prog << core.qif([0]).then(correct_q1).qelse(no_correction)
print("3-Qubit Repetition Code - Error Correction Circuit:")
print(prog)
# Run the circuit
machine = core.CPUQVM()
machine.run(prog, shots=1000)
print("Error correction results:", machine.result().get_counts())Syndrome Decoding Table
The syndrome bits
| Error Location | Correction | ||
|---|---|---|---|
| 0 | 0 | None | Identity |
| 1 | 0 | Qubit 0 | |
| 1 | 1 | Qubit 1 | |
| 0 | 1 | Qubit 2 |
The minimum Hamming distance of the repetition code is
Iterative Phase Estimation
Iterative phase estimation (IPE) determines the phase
The key circuit for each iteration
where
from pyqpanda3 import core
import math
# Iterative Phase Estimation for a unitary U with eigenvalue e^(2*pi*i*phi)
# We estimate phi to n bits of precision using n iterations.
# Each iteration uses one auxiliary qubit and one measurement.
# The target unitary is a rotation gate. For RZ(theta), the eigenvalue
# on |1⟩ is e^(-i*theta/2), so phi = -theta / (4*pi).
# We use controlled-U^(2^k) in iteration k.
n_bits = 3 # Number of precision bits
true_phase = math.pi / 4 # True phase phi for this example
# Storage for the estimated bits
phase_bits = []
for k in range(n_bits):
prog = core.QProg()
# Auxiliary qubit: q0
# Target qubit: q1 (eigenstate of U)
# Initialize auxiliary in superposition
prog << core.H(0)
# Prepare the eigenstate |1⟩ on the target qubit
prog << core.X(1)
# Apply controlled-U^(2^(n-1-k)) between iterations
# For this example, U = RZ(2*pi*phi), so U^(2^m) = RZ(2*pi*phi*2^m)
power = n_bits - 1 - k
rotation_angle = 2 * math.pi * true_phase * (2 ** power)
prog << core.CRZ(0, 1, rotation_angle)
# Apply inverse phase correction based on previously measured bits
# This compensates for the lower-order bits already determined
for j, bit_val in enumerate(phase_bits):
if bit_val == 1:
correction_angle = -2 * math.pi * (2 ** (power - j - 1)) / (2 ** n_bits)
prog << core.RZ(0, correction_angle)
# Final Hadamard on auxiliary
prog << core.H(0)
# Measure the auxiliary qubit
prog << core.measure(0, 0)
# Run this iteration
machine = core.CPUQVM()
machine.run(prog, shots=1000)
result = machine.result().get_counts()
# The most frequent outcome determines this bit
if result:
bit = max(result, key=result.get)
phase_bits.append(int(bit))
else:
phase_bits.append(0)
# Reconstruct the estimated phase from bits
estimated_phase = sum(b * (2 ** -(i + 1)) for i, b in enumerate(phase_bits))
print(f"True phase: {true_phase:.6f}")
print(f"Estimated phase: {estimated_phase:.6f}")
print(f"Phase bits (MSB first): {phase_bits}")Step-by-Step Explanation
Each iteration
- Prepare: Initialize auxiliary qubit in
and target in eigenstate - Controlled-
: Apply the unitary raised to the power , controlled by the auxiliary - Phase correction: Apply
-rotations based on previously measured bits to remove their contribution - Measure: Measure the auxiliary qubit to extract bit
of - Reconstruct: After all iterations,
in binary fractional form
The accuracy improves as
State Verification with Dynamic Circuits
State verification confirms that a quantum system is in the intended state after preparation. Dynamic circuits enable adaptive measurement strategies that are more efficient than full state tomography.
Bell State Verification
A Bell state
from pyqpanda3 import core
# Verify a Bell state |Φ+⟩ = (|00⟩ + |11⟩) / sqrt(2)
# Strategy: prepare the state, measure both qubits, check correlation
prog = core.QProg()
# Prepare the Bell state
prog << core.H(0)
prog << core.CNOT(0, 1)
# Measure both qubits
prog << core.measure(0, 0) # cbit 0
prog << core.measure(1, 1) # cbit 1
# If both qubits agree, the state is correct
# If they disagree, flag an error by flipping an ancilla
flag_error = core.QProg()
flag_error << core.X(2) # q2 starts in |0⟩, flip to |1⟩ if error
# We cannot directly check "XOR" with qif's AND semantics alone,
# so we use a different approach: measure an XOR ancilla
# Prepare XOR of measurement results using CNOT into ancilla
prog << core.CNOT(0, 2)
prog << core.CNOT(1, 2)
prog << core.measure(2, 2)
# cbit[2] == 1 means the qubits disagreed (verification failed)
# Use conditional to apply a correction or log the result
verify_fail = core.QProg()
verify_fail << core.X(3) # Flag qubit for failure
prog << core.qif([2]).then(verify_fail).qendif()
print("Bell state verification circuit:")
print(prog)
machine = core.CPUQVM()
machine.run(prog, shots=1000)
print("Verification results:", machine.result().get_counts())GHZ State Fidelity Check
For a GHZ state
from pyqpanda3 import core
# GHZ state verification for 4 qubits
# |GHZ_4⟩ = (|0000⟩ + |1111⟩) / sqrt(2)
n_qubits = 4
prog = core.QProg()
# Prepare the GHZ state
prog << core.H(0)
for i in range(1, n_qubits):
prog << core.CNOT(0, i)
# Measure all data qubits
for i in range(n_qubits):
prog << core.measure(i, i)
# Compute pairwise parities using ancilla qubits
# Check that all adjacent pairs have even parity
ancilla_start = n_qubits
for i in range(n_qubits - 1):
anc = ancilla_start + i
prog << core.CNOT(i, anc)
prog << core.CNOT(i + 1, anc)
prog << core.measure(anc, anc)
print("GHZ state fidelity check circuit:")
print(prog)
machine = core.CPUQVM()
machine.run(prog, shots=1000)
counts = machine.result().get_counts()
print("GHZ verification results:", counts)
# If all ancilla measurements are 0, the GHZ state is validThe expected outcome is that all ancilla parity checks yield 0, confirming that the GHZ correlations are intact. Any non-zero parity indicates decoherence or preparation errors.
Common Patterns and Idioms
This section collects reusable patterns for dynamic circuit programming in pyqpanda3.
Pattern: Measure-Then-Correct
The most common dynamic circuit pattern: measure a qubit, then conditionally apply a correction gate.
from pyqpanda3 import core
def measure_and_correct(prog, measure_qubit, measure_cbit, target_qubit, correction_gate):
"""Measure a qubit and conditionally apply a correction to another qubit.
Args:
prog: The QProg to append operations to.
measure_qubit: Index of the qubit to measure.
measure_cbit: Index of the classical bit to store the result.
target_qubit: Index of the qubit to apply correction to.
correction_gate: A function that takes a qubit index and returns a gate.
"""
prog << core.measure(measure_qubit, measure_cbit)
# Build the correction branch
correction = core.QProg()
correction << correction_gate(target_qubit)
# No-op branch for the else case
no_op = core.QProg()
# Apply correction if measurement was |1⟩
prog << core.qif([measure_cbit]).then(correction).qelse(no_op)
return prog
# Usage: measure q0 and conditionally apply Z to q1
prog = core.QProg()
prog << core.H(0)
prog << core.CNOT(0, 1)
prog = measure_and_correct(prog, 0, 0, 1, core.Z)
print(prog)Pattern: Conditional Identity
When you need an explicit no-op (identity) branch for clarity, use an empty QProg:
from pyqpanda3 import core
# Pattern: explicit identity branch for documentation clarity
prog = core.QProg()
prog << core.measure(0, 0)
action_branch = core.QProg()
action_branch << core.X(1) << core.Z(2)
identity_branch = core.QProg() # Empty program = identity
prog << core.qif([0]).then(action_branch).qelse(identity_branch)
print(prog)Pattern: Chained Conditionals for Multi-Outcome Decisions
Use qelseif to implement a switch-like construct over measurement outcomes:
from pyqpanda3 import core
# Pattern: switch-like behavior with qelseif
# Suppose we measure qubits 0, 1 and want different actions for 00, 01, 10, 11
prog = core.QProg()
prog << core.H(0) << core.H(1)
prog << core.measure(0, 0)
prog << core.measure(1, 1)
# Branch for both qubits measured as 1 (AND condition)
branch_both = core.QProg()
branch_both << core.X(2)
# Branch for only qubit 0 measured as 1
branch_q0_only = core.QProg()
branch_q0_only << core.Y(2)
# Branch for only qubit 1 measured as 1
branch_q1_only = core.QProg()
branch_q1_only << core.Z(2)
# Default: neither measured as 1
branch_default = core.QProg()
branch_default << core.I(2)
# qif checks AND of all listed cbits
# qif([0,1]) -> both are 1
# Then we use additional conditions for the remaining cases
prog << core.qif([0, 1]).then(branch_both).qelse(branch_default)
print("Chained conditional pattern:")
print(prog)Pattern: Feed-Forward with Multiple Stages
In multi-stage quantum algorithms, measurement results from one stage feed into conditional operations in the next stage.
from pyqpanda3 import core
# Pattern: multi-stage feed-forward
# Stage 1: Prepare and measure
# Stage 2: Conditional operations based on Stage 1
# Stage 3: Final computation
prog = core.QProg()
# Stage 1: Prepare entangled state and measure one qubit
prog << core.H(0)
prog << core.CNOT(0, 1)
prog << core.CNOT(0, 2)
prog << core.measure(0, 0) # Measure q0, result in cbit 0
# Stage 2: Conditional entangling based on Stage 1 result
# If cbit[0] == 1, create additional entanglement on remaining qubits
stage2_entangle = core.QProg()
stage2_entangle << core.CNOT(1, 2)
stage2_entangle << core.H(1)
stage2_entangle << core.measure(1, 1) # Mid-circuit measurement
stage2_simple = core.QProg()
stage2_simple << core.H(1)
stage2_simple << core.measure(1, 1)
prog << core.qif([0]).then(stage2_entangle).qelse(stage2_simple)
# Stage 3: Final computation and measurement
prog << core.CNOT(1, 2)
prog << core.measure(2, 2)
print("Multi-stage feed-forward circuit:")
print(prog)Pattern: Repeated Attempt with qwhile
Use qwhile to retry a probabilistic operation until success. This is useful for protocols where the success probability is bounded away from 1.
from pyqpanda3 import core
# Pattern: probabilistic gate with retry
# Try to prepare |1⟩ via Hadamard+measure, retry on |0⟩
prog = core.QProg()
# Initialize and first attempt
prog << core.X(0) # Start in |1⟩
prog << core.H(0) # Put in superposition
prog << core.measure(0, 0) # Measure: 50% chance of |1⟩
# Loop body: retry if we got |0⟩
retry_body = core.QProg()
retry_body << core.H(0) # Back to superposition
retry_body << core.measure(0, 0) # Try again
# While cbit[0] == 1 (got |1⟩, loop ends since condition is met)
# Note: loop continues while condition is True, so we structure accordingly
prog << core.qwhile([0]).loop(retry_body)
print("Repeated attempt pattern:")
print(prog)Debugging Dynamic Circuits
Dynamic circuits introduce complexity that can make debugging difficult. This section covers common pitfalls and strategies for identifying and fixing issues.
Strategy 1: Print and Inspect the Program
Always print the constructed QProg before running it. The string representation reveals the gate sequence and control flow structure.
from pyqpanda3 import core
prog = core.QProg()
prog << core.H(0)
prog << core.measure(0, 0)
branch = core.QProg()
branch << core.X(1)
prog << core.qif([0]).then(branch).qendif()
# Always inspect before running
print("=== Circuit structure ===")
print(prog)Strategy 2: Run Without Dynamics First
Before adding conditional logic, verify that the static portion of your circuit produces the expected results. Build incrementally.
from pyqpanda3 import core
# Step 1: Verify the static part works
static_prog = core.QProg()
static_prog << core.H(0)
static_prog << core.CNOT(0, 1)
static_prog << core.measure([0, 1], [0, 1])
machine = core.CPUQVM()
machine.run(static_prog, shots=100)
print("Static results:", machine.result().get_counts())
# Step 2: Add dynamics only after static part is confirmed correct
dynamic_prog = core.QProg()
dynamic_prog << core.H(0)
dynamic_prog << core.CNOT(0, 1)
dynamic_prog << core.measure(0, 0)
correction = core.QProg()
correction << core.X(1)
dynamic_prog << core.qif([0]).then(correction).qendif()
dynamic_prog << core.measure(1, 1)
machine.run(dynamic_prog, shots=100)
print("Dynamic results:", machine.result().get_counts())Strategy 3: Check Classical Bit Initialization
A common bug is referencing a classical bit in a condition before measuring into it. The classical bit may have an undefined or default value.
from pyqpanda3 import core
# BUG: cbit 0 is never measured, so the condition is unreliable
buggy_prog = core.QProg()
buggy_prog << core.H(0)
# Missing: buggy_prog << core.measure(0, 0)
branch = core.QProg()
branch << core.X(1)
buggy_prog << core.qif([0]).then(branch).qendif() # cbit 0 is uninitialized!
# FIX: always measure before using a cbit in a condition
fixed_prog = core.QProg()
fixed_prog << core.H(0)
fixed_prog << core.measure(0, 0) # Now cbit 0 has a defined value
branch = core.QProg()
branch << core.X(1)
fixed_prog << core.qif([0]).then(branch).qendif()Strategy 4: Verify Loop Termination
A qwhile loop runs while all specified classical bits are 1. Ensure the loop body eventually changes the measured value to break the loop.
from pyqpanda3 import core
# POTENTIAL INFINITE LOOP: loop body does not change the condition
# bad_prog = core.QProg()
# bad_prog << core.X(0) << core.measure(0, 0) # cbit[0] = 1
# bad_loop = core.QProg()
# bad_loop << core.H(1) # Does not affect qubit 0 or cbit 0!
# bad_prog << core.qwhile([0]).loop(bad_loop) # Infinite loop!
# GOOD: loop body re-measures the conditioned qubit
good_prog = core.QProg()
good_prog << core.X(0) << core.measure(0, 0)
good_loop = core.QProg()
good_loop << core.H(0) # Change the state
good_loop << core.measure(0, 0) # Re-measure to update cbit[0]
good_prog << core.qwhile([0]).loop(good_loop) # Will terminate with ~50% chance each iterationStrategy 5: Compare Expected vs Actual Counts
After running, compare the measurement statistics against theoretical predictions. For a correctly implemented dynamic circuit, the counts should match the expected distribution.
from pyqpanda3 import core
import math
# A Bell state should give 00 and 11 with equal probability
prog = core.QProg()
prog << core.H(0)
prog << core.CNOT(0, 1)
prog << core.measure([0, 1], [0, 1])
machine = core.CPUQVM()
machine.run(prog, shots=10000)
counts = machine.result().get_counts()
# Check: only 00 and 11 should appear, each ~5000 times
total_shots = sum(counts.values())
for outcome, count in counts.items():
probability = count / total_shots
print(f" {outcome}: {count} shots ({probability:.3f})")
# Expected: 00 and 11 each with probability ~0.50
# Any 01 or 10 outcomes indicate a bug in the circuitCommon Error Messages
| Error | Cause | Fix |
|---|---|---|
QProg type mismatch | Using QCircuit in a branch | Use QProg() for all branch bodies |
| Uninitialized cbit | Condition references unmeasured cbit | Add measure() before the conditional |
| Infinite loop | qwhile body does not update condition bits | Ensure loop body re-measures the conditioned qubits |
Missing qendif | qif without termination | End every qif with .qendif() or .qelse() |
Dynamic Circuit Construction Rules
Rule 1: Use QProg for Branches
All branch bodies must be QProg objects, not QCircuit objects:
# CORRECT
branch = core.QProg()
branch << core.X(1)
prog << core.qif([0]).then(branch).qendif()
# WRONG: branch must be QProg
# branch = core.QCircuit() # Don't use QCircuitRule 2: Complete the Control Flow
Every qif must be terminated with either .qendif() or .qelse():
# CORRECT: End with qendif
prog << core.qif([0]).then(branch).qendif()
# CORRECT: End with qelse
prog << core.qif([0]).then(if_branch).qelse(else_branch)
# WRONG: Missing termination
# prog << core.qif([0]).then(branch) # Missing .qendif() or .qelse()Rule 3: Measurement Before Condition
Classical bits used in conditions must be populated by measurement before the conditional:
# CORRECT: Measure first, then use cbit in condition
prog << core.measure(0, 0)
prog << core.qif([0]).then(branch).qendif()
# WRONG: Using uninitialized cbit
# prog << core.qif([0]).then(branch).qendif() # cbit 0 not measured yetRule 4: Nested Conditionals
You can nest conditionals by including them in branch programs:
outer_if = core.QProg()
outer_if << core.X(0)
inner_if = core.QProg()
inner_if << core.Y(1)
inner_else = core.QProg()
inner_else << core.Z(1)
outer_if << core.qif([1]).then(inner_if).qelse(inner_else)
prog = core.QProg()
prog << core.H(0) << core.measure(0, 0) << core.measure(1, 1)
prog << core.qif([0]).then(outer_if).qendif()Control Flow Diagram
API Reference
qif
core.qif(cbits: list[int]) -> QIfCreates a conditional construct that checks if all specified classical bits are 1.
Parameters:
| Parameter | Type | Description |
|---|---|---|
cbits | list[int] | Classical bit addresses to check (AND condition) |
Returns: QIf object with .then() method
QIf.then
QIf.then(branch: QProg) -> QIfThenSets the if-branch program.
QIfThen.qelse
QIfThen.qelse(else_branch: QProg) -> QProgAdds an else-branch and returns the completed conditional.
QIfThen.qelseif
QIfThen.qelseif(cbits: list[int]) -> QElseifAdds another condition to check.
QIfThen.qendif
QIfThen.qendif() -> QProgEnds the conditional without an else branch.
qwhile
core.qwhile(cbits: list[int]) -> QWhileCreates a while loop that repeats while all specified classical bits are 1.
QWhile.loop
QWhile.loop(loop_body: QProg) -> QProgSets the loop body program.
Limitations
- Backend support: Not all simulators support dynamic circuits. CPUQVM supports them fully.
- Condition types: Only classical bit equality to 1 is supported (no comparison operators).
- No nested measurement re-use: A classical bit used in a condition should not be re-measured within the same conditional branch.
- Loop termination: Be careful with while loops — ensure the loop body can change the condition to prevent infinite loops.
Next Steps
- Circuit Construction - Building static circuits with QProg and QCircuit
- Simulation - Running dynamic circuits on simulators
- Quantum State Preparation - Preparing quantum states with encoding methods