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.

Qamomile v0.12.7

Qamomile v0.12.7 lets you pass a Python dictionary of coefficients into a @qkernel as a runtime parameter and index it by a loop variable — angles[i] inside the kernel, re-bound to different values on each execution — and adds the % operator on UInt handles. This release also adds a set of transpile-time checks that reject several constructs which used to transpile into wrong circuits — such as allocating a fresh qubit for a variable inside a branch without consuming the previous one, or passing the same Vector[Qubit] as two arguments of a kernel call — each now raising a specific error instead of producing a wrong circuit. Controlled qmc.pauli_evolve is now supported on QURI Parts, and it no longer drops the global phase of a Hamiltonian’s constant term, so controlled evolution of a constant-offset Hamiltonian is now correct on Qiskit, QURI Parts, and CUDA-Q.

pip install qamomile==0.12.7

Breaking Changes

This release adds transpile-time checks that reject programs which previously transpiled but produced a circuit that did not match the Python source. Each pattern below now raises a specific error at transpile time.

New Features

Dictionary coefficients as runtime parameters

A Dict[K, Float] kernel argument can now be kept as a runtime parameter and indexed by a key inside the kernel — including by a qmc.range / qmc.items loop variable. Each looked-up key becomes one runtime parameter of the emitted circuit named "<dict>[<key>]" (e.g. angles[0], coeffs[(0, 1)]), so the same transpiled executable can be re-bound with different dictionary values on each execution. Declare it by listing the argument in parameters=[...]. Symbolic key components must be UInt handles, while a fully constant key against a compile-time-bound dictionary folds to a constant (#556, #562).

import numpy as np
import qamomile.circuit as qmc
from qamomile.qiskit import QiskitTranspiler

@qmc.qkernel
def rx_by_dict(n: qmc.UInt, angles: qmc.Dict[qmc.UInt, qmc.Float]) -> qmc.Vector[qmc.Bit]:
    q = qmc.qubit_array(n, name="q")
    for i in qmc.range(n):
        q[i] = qmc.rx(q[i], angle=angles[i])   # subscript a runtime-parameter dict
    return qmc.measure(q)

transpiler = QiskitTranspiler()
executable = transpiler.transpile(rx_by_dict, bindings={"n": 3}, parameters=["angles"])
# executable.parameter_names -> ['angles[0]', 'angles[1]', 'angles[2]']
# re-bind the dict per execution:
#   executable.sample(transpiler.executor(), shots=256,
#                     bindings={"angles": {0: np.pi, 1: 0.0, 2: np.pi}})

Modulo operator on UInt

UInt handles now support the % operator (and int % UInt). This makes index expressions such as i % 2 inside a qmc.range loop transpile directly instead of raising a TypeError at trace time (#466).

import qamomile.circuit as qmc
from qamomile.qiskit import QiskitTranspiler

@qmc.qkernel
def even_indices() -> qmc.Vector[qmc.Bit]:
    q = qmc.qubit_array(4, name="q")
    for i in qmc.range(4):
        if i % 2 == 0:            # modulo on a loop variable
            q[i] = qmc.x(q[i])
    return qmc.measure(q)

transpiler = QiskitTranspiler()
executable = transpiler.transpile(even_indices)
[((1, 0, 1, 0), 256)]

qmc.pauli_evolve accepts a Hamiltonian smaller than the target

qmc.pauli_evolve now accepts a Hamiltonian that acts on fewer qubits than the target Vector[Qubit]. Each Pauli term addresses array elements positionally (a term on index i acts on q[i]), and the qubits beyond the Hamiltonian’s size evolve under the identity. A Hamiltonian acting on more qubits than the target now fails with a clear EmitError at transpile time (#471).

import qamomile.circuit as qmc
import qamomile.observable as qm_o
from qamomile.qiskit import QiskitTranspiler

@qmc.qkernel
def evolve(h: qmc.Observable, t: qmc.Float) -> qmc.Vector[qmc.Bit]:
    q = qmc.qubit_array(2, name="q")
    q = qmc.h(q)
    q = qmc.pauli_evolve(q, h, t)   # h acts on 1 qubit; q[1] evolves under the identity
    return qmc.measure(q)

transpiler = QiskitTranspiler()
executable = transpiler.transpile(evolve, bindings={"h": qm_o.Z(0), "t": 0.5})

Internal Changes

Hamiltonian serialization round-trip

The IR serializer now understands bound Hamiltonians. A compiled Block that carries operator data — for example a Vector[Observable] argument bound to a list of Pauli Hamiltonians, held in a ParamSlot.bound_value — used to silently lose that data when passed through dump_json / load_json (and the msgpack pair). As of this release, each Hamiltonian round-trips back identical (#475, #548).

import qamomile.circuit as qmc
import qamomile.observable as qm_o
from qamomile.circuit.transpiler.passes.inline import InlinePass
from qamomile.circuit.ir.serialize import dump_json, load_json

@qmc.qkernel
def trotter(q: qmc.Vector[qmc.Qubit], hs: qmc.Vector[qmc.Observable], t: qmc.Float) -> qmc.Vector[qmc.Bit]:
    for i in qmc.range(hs.shape[0]):
        q = qmc.pauli_evolve(q, hs[i], t)
    return qmc.measure(q)

hamiltonians = [1.2 * qm_o.Z(0), 0.8 * qm_o.X(0)]
block = InlinePass().run(trotter.build(hs=hamiltonians, parameters=["t"]))
restored = load_json(dump_json(block))
slot = next(s for s in restored.param_slots if s.name == "hs")
print(slot.bound_value)
[Hamiltonian((Z0,): 1.2), Hamiltonian((X0,): 0.8)]

Bug Fixes

Documentation

Learn More