Skip to content

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
mermaid
graph TD
    A[Start] --> B[Quantum Operations]
    B --> C[Mid-circuit Measurement]
    C --> D{Classical Condition}
    D -->|True| E[Branch A]
    D -->|False| F[Branch B]
    E --> G[Continue]
    F --> G
    G --> H[More Operations]
    H --> I[Final Measurement]

    style C fill:#FF9800,color:#fff
    style D fill:#2196F3,color:#fff

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:

python
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)

运行结果:

          ┌─┐                           
q_0:  |0>─┤X├ ──────────── ─── ──────── 
          ├─┤ ┌──────────  ┌─┐ ───────┐ 
q_1:  |0>─┤I├ ┤1 QIF     ─ ┤X├  QENDIF├ 
          └─┘ └╥─────────  └─┘ ───────┘ 
 c :   / ══════╩═══════════════════════
               1:All 0b1

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:

python
from pyqpanda3 import core

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)

运行结果:

          ┌─┐ ┌──────────     ┌──────── ┌─┐ ───────┐ 
q_0:  |0>─┤X├ ┤0 QIF      ─── ┤0 QELSE  ┤X├  QENDIF├ 
          ├─┤ ┤           ┌─┐ ┤         └─┘        ├ 
q_1:  |0>─┤I├ ┤1          ┤X├ ┤1        ───        ├ 
          └─┘║└╥───────── └─┘ └────────     ───────┘ 
 c :   / ════╩══════════════════════════════════════
             1:All 0b1

Using qelseif for Multi-Condition Branching ​

Chain multiple conditions using qelseif:

python
from pyqpanda3 import core

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)

运行结果:

          ┌─┐ ┌──────────      ┌────────────  ┌─┐ ┌──────────      ┌────────        ───────┐ 
q_0:  |0>─┤X├ ┤0 QIF     ─ ─── ┤0 QELSEIF   ─ ┤X├ ┤0 QELSEIF ─ ─── ┤0 QELSE  ───*──  QENDIF├ 
          ├─┤ ┤            ┌─┐ ┤              └─┘ ┤                ┤         ┌──┴─┐        ├ 
q_1:  |0>─┤I├ ┤1         ─ ┤X├ ┤1           ─ ─── ┤1         ─ ─── ┤1        ┤CNOT├        ├ 
          ├─┤ ┤            └─┘ ┤                  ┤            ┌─┐ ┤         └────┘        ├ 
q_2:  |0>─┤I├ ┤2         ─ ─── ┤2           ─ ─── ┤2         ─ ┤X├ ┤2        ──────        ├ 
          ├─┤ └╥─────────      └╥───────────      └╥─────────  └─┘ └────────        ───────┘ 
q_3:  |0>─┤I├ ─║────────── ─── ─║──────────── ─── ─║────────── ─── ───────── ────── ──────── 
          └─┘  ║                ║                  ║                                         
 c :   / ══════╩════════════════╩══════════════════╩════════════════════════════════════════
               1:All 0b1        0,3:All 0b1        2:All 0b1

The flow is:

mermaid
graph TD
    A["Check cbits [1]"] -->|All 1| B[Execute branch_1: X 1]
    A -->|Not all 1| C["Check cbits [0,3]"]
    C -->|All 1| D[Execute branch_2: X 0]
    C -->|Not all 1| E["Check cbits [2]"]
    E -->|All 1| F[Execute branch_3: X 2]
    E -->|Not all 1| G[Execute branch_4: CNOT 0,1]

    style A fill:#2196F3,color:#fff
    style C fill:#2196F3,color:#fff
    style E fill:#2196F3,color:#fff

Condition Semantics ​

The condition qif([c0, c1, c2]) checks if ALL specified classical bits are 1 (logical AND):

  • qif([0]): True if cbit 0 == 1
  • qif([0, 1]): True if cbit 0 == 1 AND cbit 1 == 1
  • qif([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 ​

python
from pyqpanda3 import core

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)

运行结果:

          ┌─┐  ┌─┐ 
q_0:  |0>─┤X├ ─┤M├ 
          └─┘  └╥┘ 
q_1:  |0>──── ──╫─ 
                ║  
 c :   / ═══════╩═
                 0

The loop executes as:

mermaid
sequenceDiagram
    participant P as Program
    participant M as Measure
    participant L as Loop Body

    P->>M: Measure qubit 0 → cbit 0
    loop While cbit[0] == 1
        M->>L: X(1), X(0)
        L->>M: Measure qubit 0 → cbit 0
    end
    Note over P: Continue after loop

Practical Examples ​

Example 1: Repeat-Until-Success ​

Use dynamic circuits to repeat an operation until a desired measurement outcome:

python
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)

运行结果:

Repeat-until-success circuit:

          ┌─┐  ┌─┐ 
q_0:  |0>─┤H├ ─┤M├ 
          └─┘  └╥┘ 
 c :   / ═══════╩═
                 0

Example 2: Mid-Circuit Measurement with Conditional Gate ​

Perform a measurement and conditionally apply a correction:

python
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())

运行结果:

Mid-circuit measurement with correction:

          ┌─┐               ┌─┐                                    
q_0:  |0>─┤H├ ───*── ───────┤M├ ──────────── ─── ──────── ──────── 
          └─┘ ┌──┴─┐        └╥┘ ┌──────────  ┌─┐ ───────┐  ┌─┐     
q_1:  |0>──── ┤CNOT├ ───*────╫─ ┤1 QIF     ─ ┤Z├  QENDIF├ ─┤M├──── 
              └────┘ ┌──┴─┐  ║  ┤            ├─┤        ├  └╥┘ ┌─┐ 
q_2:  |0>──── ────── ┤CNOT├──╫─ ┤2         ─ ┤Z├        ├ ──╫──┤M├ 
                     └────┘  ║  └╥─────────  └─┘ ───────┘   ║  └╥┘ 
 c :   / ════════════════════╩═══╩══════════════════════════╩═══╩═
                              0  0:All 0b1                   1   2


{'000': 508, '111': 492}

Example 3: Error Detection with Parity Check ​

Use dynamic circuits for error detection using parity measurements:

python
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)

运行结果:

Error detection circuit:

          ┌─┐                                  
q_0:  |0>─┤H├ ───*── ───*── ───*── ────── ──── 
          └─┘ ┌──┴─┐    │      │               
q_1:  |0>──── ┤CNOT├ ───┼── ───┼── ───*── ──── 
              └────┘ ┌──┴─┐    │      │        
q_2:  |0>──── ────── ┤CNOT├ ───┼── ───┼── ──── 
                     └────┘ ┌──┴─┐ ┌──┴─┐  ┌─┐ 
q_3:  |0>──── ────── ────── ┤CNOT├ ┤CNOT├ ─┤M├ 
                            └────┘ └────┘  └╥┘ 
 c :   / ═══════════════════════════════════╩═
                                             0

Example 4: Adaptive Phase Estimation ​

Use dynamic circuits for iterative phase estimation:

python
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())

运行结果:

Adaptive phase estimation circuit:

          ┌─┐ ┌─┐               ┌─┐                                            
q_0:  |0>─┤H├ ┤H├───────────── ─┤M├─────────── ───────────────── ──────── ──── 
          ├─┤ ├─┴────────────┐  └╥┼──────────  ┌───────────────┐ ───────┐  ┌─┐ 
q_1:  |0>─┤X├ ┤RZ(0.78539816)├ ──╫┤1 QIF     ─ ┤RZ(-1.57079633)├  QENDIF├ ─┤M├ 
          └─┘ └──────────────┘   ║└╥─────────  └───────────────┘ ───────┘  └╥┘ 
 c :   / ════════════════════════╩═╩════════════════════════════════════════╩═
                                  00:All 0b1                                 1


{'10': 1000}

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:

  1. Alice and Bob share an entangled Bell pair (|Φ+⟩=12(|00⟩+|11⟩))
  2. Alice performs a Bell measurement on her qubit and the unknown state
  3. Alice sends the two classical bits to Bob
  4. Bob applies corrections based on the measurement outcomes
|ψ⟩⊗|Φ+⟩=12∑a,b∈{0,1}|a,b⟩⊗XbZa|ψ⟩
mermaid
sequenceDiagram
    participant S as Source |psi⟩
    participant A as Alice
    participant B as Bob
    participant C as Classical Channel

    S->>A: Prepare unknown |psi⟩ on q0
    A->>B: Share Bell pair (q1, q2)
    A->>A: Bell measurement on (q0, q1)
    A->>C: Send classical bits (a, b)
    C->>B: Receive (a, b)
    B->>B: Apply Z^a then X^b on q2
    Note over B: q2 now holds |psi⟩
python
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)

运行结果:

Quantum Teleportation Circuit:

          ┌──────────────┐                     ┌─┐     ┌─┐                                                           
q_0:  |0>─┤RY(0.70000000)├ ────── ───*──────── ┤H├─── ─┤M├──────── ──────── ──────────── ─── ───────── ──────── ──── 
          ├─┬────────────┘        ┌──┴─┐       └┬┴┐    └╥┘                                                           
q_1:  |0>─┤H├───────────── ───*── ┤CNOT├────── ─┤M├── ──╫───────── ──────── ──────────── ─── ───────── ──────── ──── 
          └─┘              ┌──┴─┐ ├────┴─────   └╥┼─┐   ║┌──────── ───────┐ ┌──────────  ┌─┐ ┌──────── ───────┐  ┌─┐ 
q_2:  |0>───────────────── ┤CNOT├ ┤2 QIF     ─ ──╫┤Z├ ──╫┤2 QELSE   QENDIF├ ┤2 QIF     ─ ┤X├ ┤2 QELSE   QENDIF├ ─┤M├ 
                           └────┘ └╥─────────    ║└─┘   ║└──────── ───────┘ └╥─────────  └─┘ └──────── ───────┘  └╥┘ 
 c :   / ══════════════════════════╩═════════════╩══════╩════════════════════╩════════════════════════════════════╩═
                                   0:All 0b1      1      0                   1:All 0b1                             2


Teleportation results: {'101': 31, '100': 39, '110': 29, '000': 204, '111': 22, '010': 236, '011': 207, '001': 232}

The correction rules are:

Measurement (q0, q1)Correction on q2
(0, 0)None
(0, 1)X
(1, 0)Z
(1, 1)ZX

After correction, qubit 2 holds the original state |ψ⟩ exactly. The fidelity of teleportation is perfect in the absence of noise, which is guaranteed by the no-cloning theorem -- the original state on qubit 0 is destroyed during measurement.


Quantum Error Correction Example ​

The 3-qubit repetition code protects a single logical qubit against bit-flip (X) errors. The encoding maps:

|0⟩L=|000⟩,|1⟩L=|111⟩

The syndrome extraction uses two ancilla qubits to detect which data qubit (if any) experienced a bit-flip. The syndrome (s1,s2) uniquely identifies the error location.

mermaid
graph TD
    subgraph "3-Qubit Repetition Code"
        A["Encode: |psi⟩ → |psi⟩_L"] --> B[Noise: possible X error]
        B --> C[Syndrome Extraction]
        C --> D{Syndrome s1,s2}
        D -->|"00"| E[No error]
        D -->|"01"| F[Error on q0: apply X]
        D -->|"10"| G[Error on q1: apply X]
        D -->|"11"| H[Error on q2: apply X]
        E --> I[Decode / Continue]
        F --> I
        G --> I
        H --> I
    end

    style C fill:#FF9800,color:#fff
    style D fill:#2196F3,color:#fff
python
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())

运行结果:

3-Qubit Repetition Code - Error Correction Circuit:

          ┌──────────────┐                                                                              
q_0:  |0>─┤RY(1.20000000)├ ───*── ───*─── ───*── ────── ────────── ──────────── ──── ───────── ──────── 
          └──────────────┘ ┌──┴─┐    │┌─┐    │                     ┌──────────  ┌─┐  ┌──────── ───────┐ 
q_1:  |0>───────────────── ┤CNOT├ ───┼┤X├ ───┼── ───*── ───*────── ┤1 QIF     ─ ┤X├─ ┤1 QELSE   QENDIF├ 
                           └────┘ ┌──┴┴┬┘    │      │      │       └╥───@─────  └─┘  └──────── ───────┘ 
q_2:  |0>───────────────── ────── ┤CNOT├─ ───┼── ───┼── ───┼────── ───*─║────── ──── ───────── ──────── 
                                  └────┘  ┌──┴─┐ ┌──┴─┐    │   ┌─┐    │║║                               
q_3:  |0>───────────────── ────── ─────── ┤CNOT├ ┤CNOT├ ───┼───┤M├ ───┼║─────── ──── ───────── ──────── 
                                          └────┘ └────┘ ┌──┴─┐ └╥┘ ┌──┴@┐║       ┌─┐                    
q_4:  |0>───────────────── ────── ─────── ────── ────── ┤CNOT├──╫─ ┤CNOT├║───── ─┤M├ ───────── ──────── 
                                                        └────┘  ║  └@───┘║       └╥┘                    
 c :   / ═══════════════════════════════════════════════════════╩═══╩═════════════╩════════════════════
                                                                 0  0:All 0b1      1                   


Error correction results: {'11': 1000}

Syndrome Decoding Table ​

The syndrome bits (s1,s2) identify which qubit experienced a bit-flip:

s1s2Error LocationCorrection
00NoneIdentity
10Qubit 0X0
11Qubit 1X1
01Qubit 2X2

The minimum Hamming distance of the repetition code is d=3, which allows correction of up to t=⌊(d−1)/2⌋=1 bit-flip error. This code cannot correct phase-flip (Z) errors. To protect against both, one would use the 9-qubit Shor code or the 7-qubit Steane code.


Iterative Phase Estimation ​

Iterative phase estimation (IPE) determines the phase ϕ of an eigenvalue e2πiϕ of a unitary operator U one bit at a time, starting from the least significant bit. Unlike standard QPE which requires many auxiliary qubits, IPE uses only one auxiliary qubit and repeated measurements.

The key circuit for each iteration k is:

Prob(mk=0)=1+cos⁡(2π(2kϕ−δk))2

where δk is the accumulated phase correction from previous iterations.

mermaid
graph TD
    A["Iteration k = n-1 (LSB)"] --> B["Measure bit k"]
    B --> C["Iteration k = n-2"]
    C --> D["Measure bit k"]
    D --> E["..."]
    E --> F["Iteration k = 0 (MSB)"]
    F --> G["Measure bit k"]
    G --> H["Reconstruct phi = 0.b1 b2 ... bn"]

    style A fill:#4CAF50,color:#fff
    style F fill:#4CAF50,color:#fff
    style H fill:#FF9800,color:#fff
python
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}")

运行结果:

True phase:      0.785398
Estimated phase:  0.875000
Phase bits (MSB first): [1, 1, 1]

Step-by-Step Explanation ​

Each iteration k (from k=n−1 down to k=0) performs:

  1. Prepare: Initialize auxiliary qubit in |+⟩ and target in eigenstate |ψ⟩
  2. Controlled-U2k: Apply the unitary U raised to the power 2k, controlled by the auxiliary
  3. Phase correction: Apply Z-rotations based on previously measured bits to remove their contribution
  4. Measure: Measure the auxiliary qubit to extract bit k of ϕ
  5. Reconstruct: After all iterations, ϕ≈0.b1b2⋯bn in binary fractional form

The accuracy improves as n increases. With n bits, the resolution is Δϕ=2−n.


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 |Φ+⟩=12(|00⟩+|11⟩) should yield perfectly correlated measurement outcomes. We can verify this using conditional checks.

python
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())

运行结果:

Bell state verification circuit:

          ┌─┐                  ┌─┐                               
q_0:  |0>─┤H├───────── ───*── ─┤M├─────────── ───*── ────── ──── 
          └─┘          ┌──┴─┐  └╥┘ ┌─┐           │               
q_1:  |0>───────────── ┤CNOT├ ──╫──┤M├─────── ───┼── ───*── ──── 
                       └────┘   ║  └╥┘        ┌──┴─┐ ┌──┴─┐  ┌─┐ 
q_2:  |0>───────────── ────── ──╫───╫──────── ┤CNOT├ ┤CNOT├ ─┤M├ 
          ┌──────────  ┌─┐      ║   ║───────┐ └────┘ └────┘  └╥┘ 
q_3:  |0>─┤3 QIF     ─ ┤X├─── ──╫───╫ QENDIF├ ────── ────── ──╫─ 
          └╥─────────  └─┘      ║   ║───────┘                 ║  
 c :   / ══╩════════════════════╩═══╩═════════════════════════╩═
           2:All 0b1             0   1                         2


Verification results: {'011': 484, '000': 516}

GHZ State Fidelity Check ​

For a GHZ state |GHZn⟩=12(|0⟩⊗n+|1⟩⊗n), all qubits should be perfectly correlated. We verify by checking pairwise parity.

python
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 valid

运行结果:

GHZ state fidelity check circuit:

          ┌─┐                               ┌─┐                                                            
q_0:  |0>─┤H├ ───*── ───*────── ───*────── ─┤M├──── ───*── ────── ────────── ────── ────────── ────── ──── 
          └─┘ ┌──┴─┐    │   ┌─┐    │        └╥┘        │                                                   
q_1:  |0>──── ┤CNOT├ ───┼───┤M├ ───┼────── ──╫───── ───┼── ───*── ───────*── ────── ────────── ────── ──── 
              └────┘ ┌──┴─┐ └╥┘    │   ┌─┐   ║         │      │          │                                 
q_2:  |0>──── ────── ┤CNOT├──╫─ ───┼───┤M├ ──╫───── ───┼── ───┼── ───────┼── ───*── ───────*── ────── ──── 
                     └────┘  ║  ┌──┴─┐ └╥┘   ║  ┌─┐    │      │          │      │          │               
q_3:  |0>──── ────── ────────╫─ ┤CNOT├──╫─ ──╫──┤M├ ───┼── ───┼── ───────┼── ───┼── ───────┼── ───*── ──── 
                             ║  └────┘  ║    ║  └╥┘ ┌──┴─┐ ┌──┴─┐  ┌─┐   │      │          │      │        
q_4:  |0>──── ────── ────────╫─ ────────╫─ ──╫───╫─ ┤CNOT├ ┤CNOT├ ─┤M├───┼── ───┼── ───────┼── ───┼── ──── 
                             ║          ║    ║   ║  └────┘ └────┘  └╥┘┌──┴─┐ ┌──┴─┐  ┌─┐   │      │        
q_5:  |0>──── ────── ────────╫─ ────────╫─ ──╫───╫─ ────── ────── ──╫─┤CNOT├ ┤CNOT├ ─┤M├───┼── ───┼── ──── 
                             ║          ║    ║   ║                  ║ └────┘ └────┘  └╥┘┌──┴─┐ ┌──┴─┐  ┌─┐ 
q_6:  |0>──── ────── ────────╫─ ────────╫─ ──╫───╫─ ────── ────── ──╫─────── ────── ──╫─┤CNOT├ ┤CNOT├ ─┤M├ 
                             ║          ║    ║   ║                  ║                 ║ └────┘ └────┘  └╥┘ 
 c :   / ════════════════════╩══════════╩════╩═══╩══════════════════╩═════════════════╩═════════════════╩═
                              1          2    0   3                  4                 5                 6


GHZ verification results: {'0001111': 499, '0000000': 501}

The 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.

python
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)

运行结果:

          ┌─┐         ┌─┐                                   
q_0:  |0>─┤H├ ───*── ─┤M├─────────── ─── ───────── ──────── 
          └─┘ ┌──┴─┐  └╥┼──────────  ┌─┐ ┌──────── ───────┐ 
q_1:  |0>──── ┤CNOT├ ──╫┤1 QIF     ─ ┤Z├ ┤1 QELSE   QENDIF├ 
              └────┘   ║└╥─────────  └─┘ └──────── ───────┘ 
 c :   / ══════════════╩═╩═════════════════════════════════
                        00:All 0b1

Pattern: Conditional Identity ​

When you need an explicit no-op (identity) branch for clarity, use an empty QProg:

python
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)

运行结果:

           ┌─┐                                   
q_0:  |0>──┤M├─────────── ─── ───────── ──────── 
           └╥┼──────────  ┌─┐ ┌──────── ───────┐ 
q_1:  |0>───╫┤1 QIF     ─ ┤X├ ┤1 QELSE   QENDIF├ 
            ║┤            ├─┤ ┤                ├ 
q_2:  |0>───╫┤2         ─ ┤Z├ ┤2               ├ 
            ║└╥─────────  └─┘ └──────── ───────┘ 
 c :   / ═══╩═╩═════════════════════════════════
             00:All 0b1

Pattern: Chained Conditionals for Multi-Outcome Decisions ​

Use qelseif to implement a switch-like construct over measurement outcomes:

python
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)

运行结果:

Chained conditional pattern:

          ┌─┐             ┌─┐                              
q_0:  |0>─┤H├─────────── ─┤M├────── ───────── ─── ──────── 
          ├─┤             └╥┘ ┌─┐                          
q_1:  |0>─┤H├─────────── ──╫──┤M├── ───────── ─── ──────── 
          ├─┴──────────    ║  └╥┼─┐ ┌──────── ┌─┐ ───────┐ 
q_2:  |0>─┤2 QIF       ─ ──╫───╫┤X├ ┤2 QELSE  ┤I├  QENDIF├ 
          └╥───────────    ║   ║└─┘ └──────── └─┘ ───────┘ 
 c :   / ══╩═══════════════╩═══╩══════════════════════════
           0,1:All 0b1      0   1

Pattern: Feed-Forward with Multiple Stages ​

In multi-stage quantum algorithms, measurement results from one stage feed into conditional operations in the next stage.

python
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)

运行结果:

Multi-stage feed-forward circuit:

          ┌─┐                ┌─┐                                                                    
q_0:  |0>─┤H├ ───*── ───*── ─┤M├─────────── ────── ─── ──── ───────── ─── ──── ──────── ────── ──── 
          └─┘ ┌──┴─┐    │    └╥┼──────────         ┌─┐  ┌─┐ ┌──────── ┌─┐  ┌─┐ ───────┐             
q_1:  |0>──── ┤CNOT├ ───┼── ──╫┤1 QIF     ─ ───*── ┤H├ ─┤M├ ┤1 QELSE  ┤H├ ─┤M├  QENDIF├ ───*── ──── 
              └────┘ ┌──┴─┐   ║┤            ┌──┴─┐ └─┘  └╥┘ ┤         └─┘  └╥┘        ├ ┌──┴─┐  ┌─┐ 
q_2:  |0>──── ────── ┤CNOT├ ──╫┤2         ─ ┤CNOT├ ─── ──╫─ ┤2        ─── ──╫─        ├ ┤CNOT├ ─┤M├ 
                     └────┘   ║└╥─────────  └────┘       ║  └────────       ║  ───────┘ └────┘  └╥┘ 
 c :   / ═════════════════════╩═╩════════════════════════╩══════════════════╩════════════════════╩═
                               00:All 0b1                 1                  1                    2

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.

python
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)

运行结果:

Repeated attempt pattern:

          ┌─┐ ┌─┐  ┌─┐ 
q_0:  |0>─┤X├ ┤H├ ─┤M├ 
          └─┘ └─┘  └╥┘ 
 c :   / ═══════════╩═
                     0

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.

python
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)

运行结果:

=== Circuit structure ===

          ┌─┐           ┌─┐            
q_0:  |0>─┤H├───────── ─┤M├── ──────── 
          ├─┴────────   └╥┼─┐ ───────┐ 
q_1:  |0>─┤1 QIF     ─ ──╫┤X├  QENDIF├ 
          └╥─────────    ║└─┘ ───────┘ 
 c :   / ══╩═════════════╩════════════
           0:All 0b1      0

Strategy 2: Run Without Dynamics First ​

Before adding conditional logic, verify that the static portion of your circuit produces the expected results. Build incrementally.

python
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())

运行结果:

Static results: {'11': 50, '00': 50}
Dynamic results: {'01': 42, '00': 58}

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.

python
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.

python
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 iteration

Strategy 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.

python
from pyqpanda3 import core

# 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 circuit

运行结果:

11: 4999 shots (0.500)
  00: 5001 shots (0.500)

Common Error Messages ​

ErrorCauseFix
QProg type mismatchUsing QCircuit in a branchUse QProg() for all branch bodies
Uninitialized cbitCondition references unmeasured cbitAdd measure() before the conditional
Infinite loopqwhile body does not update condition bitsEnsure loop body re-measures the conditioned qubits
Missing qendifqif without terminationEnd 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:

python
from pyqpanda3 import core

# 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 QCircuit

Rule 2: Complete the Control Flow ​

Every qif must be terminated with either .qendif() or .qelse():

python
from pyqpanda3 import core

# 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:

python
from pyqpanda3 import core

# 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 yet

Rule 4: Nested Conditionals ​

You can nest conditionals by including them in branch programs:

python
from pyqpanda3 import core

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 ​

mermaid
graph TD
    subgraph "qif construct"
        A1[qif cbits] --> A2{.then branch}
        A2 --> A3{.qelseif cbits}
        A3 -->|more| A4{.then branch}
        A3 -->|final| A5{.qelse or .qendif}
        A2 --> A5
        A4 --> A3
    end

    subgraph "qwhile construct"
        B1[qwhile cbits] --> B2{.loop body}
        B2 --> B3{Re-check condition}
        B3 -->|True| B2
        B3 -->|False| B4[Continue]
    end

    style A1 fill:#FF9800,color:#fff
    style B1 fill:#4CAF50,color:#fff

API Reference ​

qif ​

python
core.qif(cbits: list[int]) -> QIf

Creates a conditional construct that checks if all specified classical bits are 1.

Parameters:

ParameterTypeDescription
cbitslist[int]Classical bit addresses to check (AND condition)

Returns: QIf object with .then() method

QIf.then ​

python
QIf.then(branch: QProg) -> QIfThen

Sets the if-branch program.

QIfThen.qelse ​

python
QIfThen.qelse(else_branch: QProg) -> QProg

Adds an else-branch and returns the completed conditional.

QIfThen.qelseif ​

python
QIfThen.qelseif(cbits: list[int]) -> QElseif

Adds another condition to check.

QElseif.then ​

python
QElseif.then(branch: QProg) -> QElseifThen

Sets the qelseif-branch program. The returned QElseifThen object provides .qelseif(), .qelse(), and .qendif() methods.

QElseifThen.qelse ​

python
QElseifThen.qelse(else_branch: QProg) -> QProg

Adds an else-branch to the elseif block and returns the completed conditional.

QElseifThen.qelseif ​

python
QElseifThen.qelseif(cbits: list[int]) -> QElseif

Adds another condition to check from an elseif branch.

QElseifThen.qendif ​

python
QElseifThen.qendif() -> QProg

Ends the conditional block without an else branch.

QIfThen.qendif ​

python
QIfThen.qendif() -> QProg

Ends the conditional without an else branch.

qwhile ​

python
core.qwhile(cbits: list[int]) -> QWhile

Creates a while loop that repeats while all specified classical bits are 1.

QWhile.loop ​

python
QWhile.loop(loop_body: QProg) -> QProg

Sets the loop body program.


Limitations ​

  1. Backend support: Not all simulators support dynamic circuits. CPUQVM supports them fully.
  2. Condition types: Only classical bit equality to 1 is supported (no comparison operators).
  3. No nested measurement re-use: A classical bit used in a condition should not be re-measured within the same conditional branch.
  4. Loop termination: Be careful with while loops — ensure the loop body can change the condition to prevent infinite loops.

Next Steps ​

Released under the MIT License.