Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Classical Control Flow Patterns

Tags: tutorial

Quantum circuits often have structure that depends on classical control flow: iterating over qubits, applying gates based on a graph’s edges, or choosing between gate sequences. Qamomile supports these patterns through qmc.range, qmc.items, if branching, and while loops.

This chapter covers:

qmc.range Loops

qmc.range may take start, stop, and step arguments. Here we create a qkernel that applies H to every other qubit and then entangles adjacent pairs with CX.

<Figure size 975.5x416 with 1 Axes>

qmc.items for Sparse Interaction Data

Many variational algorithms apply gates only on specific pairs of qubits, determined by a graph or interaction map. Rather than looping over all pairs, you can pass a dictionary of interactions and iterate with qmc.items().

The dictionary type uses Qamomile’s symbolic types: qmc.Dict[qmc.Tuple[qmc.UInt, qmc.UInt], qmc.Float] — keys are qubit index pairs, values are interaction weights.

Dict Subscript Lookup (d[key])

Besides iterating with qmc.items(), a qmc.Dict can be indexed directly with d[key]. The most useful pattern is indexing one dict with the iteration keys of another: iterate over sparse interaction terms in one dict while looking up per-edge scale factors in a second dict.

Inspecting with transpiler.to_circuit()

draw() does not yet support all patterns (particularly items with complex types, if, and while). In such cases, use transpiler.to_circuit() to see the concrete transpiled circuit after all parameters are bound.

     ┌───┐                                    ┌─┐   
q_0: ┤ H ├─■─────────────────────■────────────┤M├───
     ├───┤ │ZZ(0.4)              │         ┌─┐└╥┘   
q_1: ┤ H ├─■─────────■───────────┼─────────┤M├─╫────
     ├───┤           │ZZ(-0.28)  │ZZ(0.12) └╥┘ ║ ┌─┐
q_2: ┤ H ├───────────■───────────■──────────╫──╫─┤M├
     └───┘                                  ║  ║ └╥┘
c: 3/═══════════════════════════════════════╩══╩══╩═
                                            1  0  2 

Only the three edges in edge_data produce RZZ gates — no wasted operations.

if Branching and while Loops

Qamomile supports mid-circuit measurement followed by classical branching. The condition must be a measurement result (Bit), not an argument of qkernels.

This maps directly to hardware-level conditional execution: measure a qubit, then decide what to do next based on the outcome.

if on a measurement result

A common pattern: measure one qubit and conditionally apply a gate to another qubit based on the outcome.

This transpiles to a Qiskit if_else instruction and can be executed:

  bit=1: 100 shots

Since q0 is prepared as |1⟩, the measurement always yields 1, so q1 always gets flipped — every shot should return 1.

while on a measurement result

A while loop repeats until the measurement condition becomes false. This is useful for repeat-until-success protocols.

This transpiles to a Qiskit while_loop instruction. We can inspect the generated circuit structure:

     ┌───┐┌─┐                                  
q_0: ┤ H ├┤M├──────────────────────────────────
     └───┘└╥┘┌─────────      ┌───┐┌─┐ ───────┐ 
q_1: ──────╫─┤ While-0  ─|0>─┤ H ├┤M├  End-0 ├─
           ║ └────╥────      └───┘└╥┘ ───────┘ 
           ║ ┌────╨────┐           ║           
c: 1/══════╩═╡ c_0=0x1 ╞═══════════╩═══════════
           0 └─────────┘           0           

Combining if and while

You can combine both patterns. Here is a protocol that repeatedly measures and conditionally applies a correction gate:

     ┌───┐┌─┐                                                            »
q_0: ┤ H ├┤M├────────────────────────────────────────────────────────────»
     └───┘└╥┘┌─────────        ┌──────  ┌───┐┌────────  ───────┐         »
q_1: ──────╫─┤          ───────┤ If-1  ─┤ X ├┤ Else-1    End-1 ├─────────»
           ║ │ While-0         └──╥───  └───┘└────────  ───────┘ ┌───┐┌─┐»
q_2: ──────╫─┤          ─|0>──────╫──────────────────────────────┤ H ├┤M├»
           ║ └────╥────           ║                              └───┘└╥┘»
           ║ ┌────╨────┐     ┌────╨────┐                               ║ »
c: 2/══════╩═╡ c_0=0x1 ╞═════╡ c_0=0x1 ╞═══════════════════════════════╩═»
           0 └─────────┘     └─────────┘                               0 »
«                  
«q_0: ─────────────
«      ───────┐ ┌─┐
«q_1:         ├─┤M├
«       End-0 │ └╥┘
«q_2:         ├──╫─
«      ───────┘  ║ 
«c: 2/═══════════╩═
«                1 

Summary

  • qmc.range(n) for looping over symbolic ranges.

  • qmc.items(dict) for iterating over sparse key-value data (edges, weights).

  • d[key] for looking up one dict by the iteration keys of another (per-edge coefficients or calibration scales).

  • if bit: and while bit: for branching on measurement results. Both branches must handle the same qubit handles (affine rule).

  • These control flow patterns transpile to native quantum SDK instructions (e.g., Qiskit if_else and while_loop).

Next: Reuse Patterns — helper qkernels, composite gates, and stub gates for top-down design.