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()for loopsqmc.items()for iterating over dictionariesifandwhileon measurement results for mid-circuit branching
# Install the latest Qamomile through pip!
# !pip install qamomileimport qamomile.circuit as qmc
from qamomile.qiskit import QiskitTranspiler
transpiler = QiskitTranspiler()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.
@qmc.qkernel
def hadamard_chain(n: qmc.UInt) -> qmc.Vector[qmc.Bit]:
q = qmc.qubit_array(n, name="q")
# Apply H to every other qubit
for i in qmc.range(0, n, 2):
q[i] = qmc.h(q[i])
# Entangle adjacent pairs
for i in qmc.range(n - 1):
q[i], q[i + 1] = qmc.cx(q[i], q[i + 1])
return qmc.measure(q)hadamard_chain.draw(n=5, fold_loops=False)
qmc.items for Sparse Interaction Data¶
Many quantum algorithms (QAOA, VQE) 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.
@qmc.qkernel
def sparse_coupling(
n: qmc.UInt,
edges: qmc.Dict[qmc.Tuple[qmc.UInt, qmc.UInt], qmc.Float],
gamma: qmc.Float,
) -> qmc.Vector[qmc.Bit]:
q = qmc.qubit_array(n, name="q")
# Initial superposition
for i in qmc.range(n):
q[i] = qmc.h(q[i])
# Apply RZZ interactions only on specified edges
for (i, j), weight in qmc.items(edges):
q[i], q[j] = qmc.rzz(q[i], q[j], gamma * weight)
return qmc.measure(q)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.
edge_data = {(0, 1): 1.0, (1, 2): -0.7, (0, 2): 0.3}
circuit = transpiler.to_circuit(
sparse_coupling,
bindings={"n": 3, "edges": edge_data, "gamma": 0.4},
)
print(circuit) ┌───┐ ┌─┐
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.
@qmc.qkernel
def conditional_flip() -> qmc.Bit:
q0 = qmc.qubit("q0")
q1 = qmc.qubit("q1")
q0 = qmc.x(q0) # Prepare |1⟩
bit = qmc.measure(q0)
# Conditionally flip q1 based on q0's measurement
if bit:
q1 = qmc.x(q1)
else:
pass
return qmc.measure(q1)This transpiles to a Qiskit if_else instruction and can be executed:
exe = transpiler.transpile(conditional_flip)
executor = transpiler.executor()
job = exe.sample(executor, bindings={}, shots=100)
result = job.result()
for value, count in result.results:
print(f" bit={value}: {count} shots") 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.
@qmc.qkernel
def repeat_until_zero() -> qmc.Bit:
q = qmc.qubit("q")
q = qmc.h(q) # 50/50 chance of |0⟩ or |1⟩
bit = qmc.measure(q)
while bit:
# Re-prepare and re-measure until we get 0
q = qmc.qubit("q2")
q = qmc.h(q)
bit = qmc.measure(q)
return bitThis transpiles to a Qiskit while_loop instruction. We can inspect the generated circuit structure:
exe_while = transpiler.transpile(repeat_until_zero)
qc_while = exe_while.compiled_quantum[0].circuit
print(qc_while) ┌───┐┌─┐
q_0: ┤ H ├┤M├─────────────────────────────
└───┘└╥┘┌───────── ┌───┐┌─┐ ───────┐
q_1: ──────╫─┤ While-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:
@qmc.qkernel
def measure_and_correct() -> qmc.Bit:
q0 = qmc.qubit("q0")
q1 = qmc.qubit("q1")
q0 = qmc.h(q0)
bit = qmc.measure(q0)
while bit:
# If bit is 1, apply correction to q1
if bit:
q1 = qmc.x(q1)
else:
q1 = q1
# Re-prepare and re-measure
q0 = qmc.qubit("q0_retry")
q0 = qmc.h(q0)
bit = qmc.measure(q0)
return qmc.measure(q1)exe_combined = transpiler.transpile(measure_and_correct)
qc_combined = exe_combined.compiled_quantum[0].circuit
print(qc_combined) ┌───┐┌─┐ »
q_0: ┤ H ├┤M├─────────────────────────────────────────────────────────────────»
└───┘└╥┘┌───────── ┌────── ┌───┐┌──────── ───────┐ ───────┐ »
q_1: ──────╫─┤ ───────┤ If-1 ─┤ X ├┤ Else-1 End-1 ├──── ├─»
║ │ While-0 ┌───┐ └──╥─── └───┘└──────── ───────┘ ┌─┐ End-0 │ »
q_2: ──────╫─┤ ┤ H ├─────╫──────────────────────────────┤M├ ├─»
║ └────╥──── └───┘ ║ └╥┘ ───────┘ »
║ ┌────╨────┐ ┌────╨────┐ ║ »
c: 3/══════╩═╡ c_0=0x1 ╞═════╡ c_0=0x1 ╞══════════════════════════╩═══════════»
0 └─────────┘ └─────────┘ 0 »
«
«q_0: ───
« ┌─┐
«q_1: ┤M├
« └╥┘
«q_2: ─╫─
« ║
«c: 3/═╩═
« 2
Summary¶
qmc.range(n)for looping over symbolic ranges.qmc.items(dict)for iterating over sparse key-value data (edges, weights).if bit:andwhile 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_elseandwhile_loop).
Next: Reuse Patterns — helper qkernels, composite gates, and stub gates for top-down design.