Qamomile v0.12.4 renames the public API for controlling gates and quantum kernels from qmc.controlled to qmc.control and strengthens it. A single controlled gate can now take several control arguments at once, and the new control_indices keyword selects which slots of the control register act as active controls; the number of controls can also be given as a symbolic value. A new tutorial walks through the whole controlled-gate workflow end to end. On the optimization side, BinaryModel.from_higher_ising builds a BinaryModel from an Ising model with degree-3-and-higher interaction terms. It also closes a previously missing gap: you can now negate a Float handle with -theta.
pip install qamomile==0.12.4Breaking Changes¶
qmc.controlled is now qmc.control¶
The controlled-gate entry point is renamed from qmc.controlled to qmc.control; qmc.controlled is no longer importable. The signature is unchanged — qmc.control(gate, num_controls=1) — so the migration is a mechanical rename at every call site. The ControlledGate object returned by the call keeps its class name (#413).
# before (v0.12.3)
cg = qmc.controlled(qmc.x, num_controls=2)
# after (v0.12.4)
cg = qmc.control(qmc.x, num_controls=2)Controlled-gate index selection is now symbolic-mode-only¶
ControlledGate.__call__ no longer accepts the target_indices keyword, and controlled_indices is renamed to control_indices. The previous concrete index-spec form — selecting target / control slots of a single Vector by passing index lists alongside a concrete num_controls — is removed. Subset selection now lives in symbolic mode via control_indices (see New Features), so call sites that passed target_indices= / controlled_indices= must move the selection into a symbolic-num_controls call (#413).
New Features¶
Controlled gates: a more expressive qmc.control¶
qmc.control(gate, num_controls=...) turns any built-in gate (qmc.x, qmc.rx, qmc.p, ...) or user-defined @qmc.qkernel into its controlled version. Controlling a built-in without a hand-written wrapper has been possible since v0.12.2; this release renames the API to qmc.control and expands its functionality.
The following is a concrete-mode example (num_controls is a Python int):
import qamomile.circuit as qmc
from qamomile.qiskit import QiskitTranspiler
# CCY is Y with two controls; defined without a wrapper kernel.
ccy = qmc.control(qmc.y, num_controls=2)
@qmc.qkernel
def demo() -> qmc.Vector[qmc.Bit]:
q = qmc.qubit_array(3, name="q")
q[0] = qmc.x(q[0])
q[1] = qmc.x(q[1])
q[0], q[1], q[2] = ccy(q[0], q[1], q[2])
return qmc.measure(q)
exe = QiskitTranspiler().transpile(demo)In symbolic mode (num_controls is a qmc.UInt handle or a UInt expression such as n - 1) the control count is resolved at transpile time from bindings. New in this release, symbolic mode accepts a multi-argument control prefix — several positional controls (scalar Qubit, VectorView, Vector[Qubit], or a mix) whose qubit counts sum to num_controls — and a control_indices keyword that selects which slots of a single control register wire in as active controls while the rest pass through untouched:
@qmc.qkernel
def subset_demo(n: qmc.UInt, k_ctrls: qmc.UInt) -> qmc.Vector[qmc.Bit]:
pool = qmc.qubit_array(n, name="pool")
tgt = qmc.qubit(name="tgt")
pool[0] = qmc.x(pool[0])
pool[1] = qmc.x(pool[1])
pool[3] = qmc.x(pool[3]) # pool[2] stays |0> — it is the inactive slot
# Only slots 0, 1, 3 wire in as controls; pool[2] passes through untouched.
cg = qmc.control(qmc.x, num_controls=k_ctrls)
pool, tgt = cg(pool, tgt, control_indices=[0, 1, 3])
return qmc.measure(pool)
exe = QiskitTranspiler().transpile(subset_demo, bindings={"n": 4, "k_ctrls": 3})See Tutorial 04 — Controlling Gates and Sub-Kernels for the end-to-end walk-through.
Higher-order Ising models: BinaryModel.from_higher_ising¶
BinaryModel.from_higher_ising(higher_ising, constant=0.0, simplify=False) builds a SPIN-type BinaryModel from a dict mapping index tuples to coefficients, with no upper bound on the interaction degree — cubic (s_i s_j s_k), quartic, and beyond. Repeated indices within a term are reduced using the spin identity s_i**2 = 1 (so (0, 0, 2) collapses to (2,)), and the indices are renumbered to a contiguous range (#382).
from qamomile.optimization.binary_model import BinaryModel
# Linear, quadratic, and a cubic (degree-3) interaction term.
higher_ising = {(0,): 1.0, (0, 1): 2.0, (0, 1, 3): 4.0}
model = BinaryModel.from_higher_ising(higher_ising, constant=8.0)
print(model.num_bits, model.coefficients)3 {(0,): 1.0, (0, 1): 2.0, (0, 1, 2): 4.0}Unary negation of Float handles¶
A Float handle now supports unary minus, so you can write -theta directly inside a @qmc.qkernel instead of 0.0 - theta. The negation lowers to a multiplication by -1.0, so when theta is bound at compile time it folds into a literal angle in the emitted circuit (#401).
import qamomile.circuit as qmc
from qamomile.qiskit import QiskitTranspiler
@qmc.qkernel
def neg_cancel(theta: qmc.Float) -> qmc.Vector[qmc.Bit]:
q = qmc.qubit_array(1, name="q")
q = qmc.ry(q, theta)
q = qmc.ry(q, -theta) # unary minus on a Float handle; the rotations cancel
return qmc.measure(q)
exe = QiskitTranspiler().transpile(neg_cancel, bindings={"theta": 1.0})Internal Changes¶
Two internal changes round out the release. The first supports the controlled-gate work above; the second is independent.
Controlled-gate IR consolidation¶
The IR previously carried a separate IndexSpecControlledU operation for index-selected controlled gates, alongside ConcreteControlledU and SymbolicControlledU. This release retires IndexSpecControlledU and folds its capability into SymbolicControlledU, which gains two fields: num_control_args (how many leading operands form the control prefix, for the multi-argument form) and control_indices (the selected control slots, or None for the whole register). The JSON / msgpack wire format gains corresponding additive fields; because an old payload decodes to num_control_args=1 / control_indices=None, SCHEMA_VERSION stays at 1 and previously serialized blocks still load.
import qamomile.circuit as qmc
from qamomile.circuit.ir.serialize import dump_msgpack, load_msgpack
from qamomile.qiskit import QiskitTranspiler
@qmc.qkernel
def mcx_pool(n: qmc.UInt) -> qmc.Vector[qmc.Bit]:
ctrls = qmc.qubit_array(n, name="ctrls")
tgt = qmc.qubit(name="tgt")
cg = qmc.control(qmc.x, num_controls=n) # symbolic whole-pool control
ctrls, tgt = cg(ctrls, tgt)
return qmc.measure(ctrls)
affine = QiskitTranspiler().inline(mcx_pool.build(n=3))
restored = load_msgpack(dump_msgpack(affine))Why: the symbolic multi-argument control prefix and control_indices selection in New Features need a single IR operation that represents both the whole-register and subset cases without a second op type. Consolidating onto SymbolicControlledU keeps the emit passes (Qiskit, QURI Parts, CUDA-Q) on one controlled-gate code path instead of branching on three operations (#413).
Earlier, broader quantum-rebind validation¶
Forbidden reassignment of a quantum variable — rebinding a Qubit / Vector[Qubit] to a different value, overwriting a parameter with a fresh allocation, chained assignments, mismatched tuple unpacking — is now detected when the @qmc.qkernel is defined and raises QubitRebindError immediately, instead of surfacing later during build() / transpilation. The analyzer covers more of these patterns than before, and the message names the offending pattern and a suggested fix.
Why: unlike the item above, this is not part of the controlled-gate work. A rebind violation is a structural mistake in the kernel source, so reporting it at definition time — with a concrete pattern-and-fix message — catches the bug at the point the author can act on it, rather than after an unrelated transpile step. Kernels that previously slipped a rebind violation past the analyzer now fail at definition time (#403).
Bug Fixes¶
Nested kernels using shape-dependent stdlib gates now specialize correctly (#404, fixes #392). When a
@qmc.qkernelcalls another kernel that uses a shape-dependent stdlib gate (qmc.qft,qmc.iqft) on aVector[Qubit]whose size is fixed by the caller, the callee is now re-traced against the concrete register size instead of silently emitting nothing for the shape-dependent gate.Visualization polish: controlled built-in gates (including controlled-SWAP) draw with dedicated control / target symbols rather than a generic box (#409); controlled-U boxes show the wrapped gate’s name and its classical arguments (#413); deeply nested inline blocks with bound
Vectoroperands route their gates onto the correct wires (#410); andVector-slice patterns — literal-bound slices, nested slices, and slice-assignment broadcasts — render on the correct root-register wires (#408, #413).
Documentation¶
New Tutorial 04 — Controlling Gates and Sub-Kernels walks through
qmc.controlend to end: controlling built-ins and user kernels, concrete vs. symbolic mode,power=, default arguments, the multi-argument control prefix,control_indicesselection, and the errors each misuse raises.New QURI Parts Support integration article shows how to transpile a kernel to QURI Parts and run it on a Qulacs state-vector simulator, with a MaxCut QAOA example end to end (#386).
New Hybrid Quantum Neural Network (HQNN) article trains a CNN feature extractor together with a quantum variational circuit on Fashion-MNIST, using the parameter-shift rule to backpropagate through the quantum layer (#319).
The quantum error correction tutorials were rewritten: Introduction to Quantum Error Correction (3-qubit bit-flip / phase-flip codes, Shor’s 9-qubit code, stabilizers) and Stabilizer Formalism and the Steane Code (CSS construction, syndrome decoding, transversal Hadamard) (#399).
Tutorials 04–09 are renumbered to 05–10 to make room for the new Controlled Gates tutorial — e.g. the former Tutorial 04 (Resource Estimation) is now Tutorial 05, and the former Tutorial 09 (Compilation & Transpilation) is now Tutorial 10. External links to specific tutorial file paths from before v0.12.4 need updating.