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

Breaking Changes

Remove the Clifford+T Estimation Mode

Qamomile previously supported resource estimation based on a synthesis model that converted the corresponding logical operations into Clifford+T gates when basis=GateBasis.CLIFFORD_T and precision were specified for APIs such as QKernel.estimate_resources(). As part of the current focus on Qamomile’s algorithm layer, v0.15.0 removes this conversion mode. Accordingly, GateBasis, qamomile.circuit.estimator.ResourceEstimatorConfig, and the basis and precision arguments of the resource estimation APIs have been removed from the public API.

Count Measurements and Resets as Separate Resources

Previously, there were no dedicated fields for retrieving measurement and reset counts, and resets were included in gates. Starting with v0.15.0, measurements and resets can be inspected independently through measurements and resets, respectively.

import qamomile.circuit as qmc


@qmc.qkernel
def measurement_and_reset() -> qmc.Bit:
    qubit = qmc.h(qmc.qubit("qubit"))
    qubit = qmc.reset(qubit)
    return qmc.measure(qubit)


resources = measurement_and_reset.estimate_resources()

assert resources.gates.total == 1
assert resources.measurements.total == 1
assert resources.resets.total == 1

Change the Default Multi-Control Model to CLEAN_ANCILLA_TOFFOLI

Previously, a multi-controlled gate was counted as a single abstract gate regardless of its number of control qubits. Starting with v0.15.0, it is counted using a fixed resource estimation model based on Toffoli gates and ancillary qubits initialized to |0>. As a result, both the gate count and the required number of qubits may increase. To continue counting multi-controlled gates as abstract gates, specify ControlDecomposition.ABSTRACT.

import qamomile.circuit as qmc


@qmc.qkernel
def double_controlled_h() -> qmc.Bit:
    controls = qmc.qubit_array(2, "controls")
    target = qmc.qubit("target")
    controls, target = qmc.control(qmc.h, num_controls=2)(controls, target)
    return qmc.measure(target)


default = double_controlled_h.estimate_resources()
abstract = double_controlled_h.estimate_resources(
    control_decomposition=qmc.ControlDecomposition.ABSTRACT,
)

assert default.gates.total == 3
assert default.gates.toffoli == 2
assert default.width.clean_ancilla_qubits == 1
assert abstract.gates.total == 1
assert abstract.width.clean_ancilla_qubits == 0

Change Oracle Cost Callbacks to Use OpaqueCostContext

The argument passed to an Oracle cost callback has changed from OpaqueCallContext to OpaqueCostContext. The callback must return the base cost of applying the Oracle definition once, including any control qubits declared by the Oracle itself. Controls added with qmc.control() and transformations applied with qmc.inverse() are accounted for by resource estimation. Callbacks that previously adjusted costs using call-site information such as controls or transform must be updated to use fields such as target_shapes, definition_control_qubits, and target_qubits.

For example, an Oracle that applies one single-qubit gate per target qubit can define its cost as follows:

import qamomile.circuit as qmc


def linear_gate_cost(
    context: qmc.OpaqueCostContext,
) -> qmc.ResourceEstimate:
    num_targets = context.target_qubits
    return qmc.ResourceEstimate(
        gates=qmc.GateResources(
            total=num_targets,
            single_qubit=num_targets,
        ),
        control_decomposition=context.control_decomposition,
    )


oracle = qmc.Oracle(
    "linear_gate_oracle",
    signature=qmc.CallableSignature(
        inputs=[qmc.Vector[qmc.Qubit]],
        outputs=[qmc.Vector[qmc.Qubit]],
    ),
    cost=linear_gate_cost,
)

Revise EstimateQuality and the ResourceEstimate.to_dict() Schema

EstimateQuality.UPPER_BOUND and EstimateQuality.MODELED have been removed. Estimate guarantees relative to the selected circuit model are now represented by EXACT, CONSERVATIVE (never underestimates), and UNKNOWN. Whether a resource model was used is represented separately by EstimateDerivation, while the presence of a mathematical approximation is represented by ApproximationStatus. The output format of ResourceEstimate.to_dict() has also changed to include the new aggregate fields as well as derivation, approximation, control_decomposition, and requirements.

Change Oracle Identity Semantics and the Explicit Signature Contract

The TransformedOracle type introduced in v0.15.0 compares the wrapped Oracle instance, the added-control configuration, and the inverse state. Because separately constructed Oracle instances with identical attributes need not represent the same operation, Oracle equality is based on instance identity. Thus, two TransformedOracle values built from the same Oracle with the same transformation state compare equal, while values built from different Oracle instances do not. Both Oracle and TransformedOracle are hashable and can be used as dictionary keys or set elements.

num_control_qubits already declares the controls required by an Oracle definition. Including those controls again in signature= duplicated the same information and blurred the boundary between definition-level controls and controls added later with qmc.control(). Therefore, signature= must now describe only the target inputs and outputs. Qamomile automatically prefixes the internal signature with the controls declared by num_control_qubits. A signature whose arity or types do not match the actual target call raises ValueError. Invoke an Oracle with control qubits using the scalar form and pass the control qubits through controls=, rather than using the Vector form.

Feature Enhancements

Track Circuit Width, Category-Specific Depth, and Derivation Metadata in Resource Estimates

In addition to qubits, which represents the maximum number of qubits used concurrently, resource estimates now expose circuit_qubits, the static circuit width including inputs, every allocation site, and ancillary qubits. Alongside the overall depth, category-specific values such as gate_depth, measurement_depth, and reset_depth are also available. These depths preserve the dependency structure of the entire circuit, so their sum does not equal the overall depth.

The derivation field distinguishes structural analysis from resource-model values. The quality, approximation, and assumptions fields separately expose the guarantee attached to the reported counts, whether the selected circuit approximates an ideal mathematical operation, and the assumptions behind the estimate. With trace=True, the estimate also retains the derivation steps for individual operations and quantum kernels.

For example, the fields and derivation trace of the double_controlled_h kernel shown above can be accessed as follows:

resources = double_controlled_h.estimate_resources(trace=True)

assert resources.derivation is qmc.EstimateDerivation.STRUCTURAL
assert resources.quality is qmc.EstimateQuality.CONSERVATIVE
assert resources.approximation is qmc.ApproximationStatus.EXACT

assumptions = [
    (assumption.message, assumption.source)
    for assumption in resources.assumptions
]

assert resources.trace is not None
trace_text = resources.trace.render()

Amazon Braket Support

The qamomile[braket] extra now provides BraketTranspiler, BraketExecutor, and BraketExecutionOptions. Qamomile quantum kernels can be converted into Amazon Braket-native Circuit objects, with runtime parameters represented as FreeParameter values and passed through Braket inputs. When no device is specified, execution uses LocalSimulator; supplying an AwsDevice lets the same execution API target a device on AWS.

import math

import qamomile.circuit as qmc
from qamomile.braket import BraketTranspiler


@qmc.qkernel
def parameterized_bell(theta: qmc.Float) -> qmc.Vector[qmc.Bit]:
    qubits = qmc.qubit_array(2, "qubits")
    qubits[0] = qmc.ry(qubits[0], theta)
    qubits[0], qubits[1] = qmc.cx(qubits[0], qubits[1])
    return qmc.measure(qubits)


transpiler = BraketTranspiler()
executable = transpiler.transpile(parameterized_bell, parameters=["theta"])
sample = executable.sample(
    transpiler.executor(),
    shots=32,
    bindings={"theta": math.pi},
).result()

assert sample.results == [((1, 1), 32)]

In addition to sampling, Hamiltonian expectation values support Exact() and ShotBased(shots). The current BraketTranspiler targets static gate-model circuits and therefore cannot transpile measurement-dependent if/while control flow, resets, or mid-circuit measurements followed by reuse of the same qubit. Experimental dynamic-circuit capabilities offered by some Amazon Braket devices are outside the scope of v0.15.0. Exact() on QPUs and TargetPrecision on any device are also unsupported. Batch retries default to zero to avoid unintentionally resubmitting billable tasks.

Add a Common Asynchronous Execution Lifecycle and Reconnection to Submitted Remote Jobs Across Engines

The Job base class now holds an engine-independent ExecutionHandle. The common API provides result(timeout=...), result_async(timeout=...), normalized status(), provider-specific raw_status(), cancellation, metadata, and access to the native task. qBraid now returns a Job immediately after submission instead of waiting for provider-side completion, allowing status checks and result retrieval through the same lifecycle.

ExecutionCapabilities reports whether an executor supports asynchronous sampling and estimation, expectation-value estimation, cancellation, reconnection to submitted remote jobs, native batch execution, and which per-request estimation-accuracy policies it accepts. Use Exact(), ShotBased(shots), or TargetPrecision(precision) to select the desired accuracy for each execution through ExecutableProgram.run(estimation=...).

For engines that support reconnection, job.snapshot() creates a reference that contains no credentials, and ExecutableProgram.restore() reconnects to the submitted remote job while retaining the same typed result conversion. Runtime bindings are not stored in the snapshot and must be supplied again when reconnecting. Existing synchronous executors are treated as completed handles, so existing execute() implementations continue to work unchanged.

A timeout passed to result(timeout=...) now limits the total wait for a job containing multiple tasks. Successful results are cached, and Amazon Braket also caches failures, so repeated calls to result() on the same job do not make unnecessary requests to the remote service.

Compose control and inverse Transformations for Oracles

Oracle transformations can now combine qmc.inverse(oracle), qmc.control(qmc.inverse(oracle)), qmc.inverse(qmc.control(oracle)), and nested qmc.control(...) calls. These transformations are represented by the public TransformedOracle type, preserving open-control patterns, inversion, and fixed resource costs through serialization. Controls can be added only to fixed-width, scalar-signature Oracles; controls on vector-signature Oracles are not currently supported. The num_controls argument must be a concrete positive integer.

Strengthen Shape and Rank Validation for Array Bindings, Including Matrix

Shape detection for lists, tuples, and NumPy arrays passed to Vector, Matrix, and Tensor has been unified. Array values supplied through bindings are checked for rectangularity, agreement with the declared rank, and whether every element falls within the declared type’s valid range. Runtime parameters use the same shape detection, so ragged arrays and invalid shapes are consistently reported as ValueError at an early stage.

Strengthen Validation of @qkernel Return Annotations

Qamomile already checked some aspects of @qkernel return structure, such as whether a value was scalar or an array and the number of elements in a Python tuple. However, it did not consistently compare value types such as Bit and Qubit, the rank of Vector, Matrix, and Tensor, or the types and order of tuple elements. In addition, when a quantum kernel declared tuple[T] and produced one output, calling it returned the bare T value instead of preserving the declared tuple structure.

v0.15.0 strengthens static contract checking for quantum-kernel types by validating return annotations against the type and structure of the returned value during build() or transpilation. Mismatches are reported early as TypeError, and no return value, an empty tuple, a scalar, and a single-element Python tuple are now treated as distinct structures. Consequently, calling a quantum kernel declared with tuple[T] returns (value,). Update return annotations to match the actual value, replace variable-length tuple[T, ...] annotations with fixed-length tuples, and use qmc.Tuple for nested Python tuples.

Bugfixes

Fix Single-Qubit Grover Diffusion

When grover_search() received a one-qubit search register, the diffusion operator constructed mcx with an empty control register, causing transpilation and resource estimation to fail. The one-qubit case now applies a Z gate directly—the zero-control specialization of a multi-controlled Z. This makes resource estimation available for the one-qubit case and allows the circuit to be transpiled and executed across supported engines, with marked-state probabilities and expectation values matching analytic results.

Fix Resource Accounting for Trotter Expansion, Measurement, and Array Access

Resource estimation now accounts for Suzuki–Trotter expansion according to its order and number of steps, and correctly tracks measurement-dependent branches, dependencies on individual array elements, and the ranges of symbolic indices and slices. This resolves inaccurate gate counts, width, and depth, as well as failures to estimate valid quantum kernels containing these constructs.

Fix Validation of Surface-Code Distance and Invalid Numeric Inputs

surface_code_estimate() now rounds the surface-code distance up to an odd integer of at least 3 and supports circuits with no non-Clifford gates. It also rejects non-numeric, negative, or non-finite logical resource values and non-positive or non-finite physical-model coefficients at an earlier stage.

Fix Overloads, Return Types, and Integer Validation for Oracle, Grover, and Modular-Arithmetic APIs

The overloads for Oracle calls with Vector or VectorView, modmul_const() with or without control, and grover_iteration_count() with concrete or symbolic values now allow static type checkers to infer their actual return types correctly. Oracle.num_qubits and Oracle.num_control_qubits accept NumPy integers while rejecting booleans, floats, and negative values; grover_iteration_count() treats Python and NumPy integers as concrete values while rejecting booleans and non-positive values.

Fix Unused Runtime Parameters Being Required

Quantum kernels transformed with control or inverse no longer require runtime parameters that do not remain in the circuit. The ordering of parameters that are actually used is preserved.

Show Allocated Qubit Names in Affine Diagnostics

When reusing a named qubit raises QubitConsumedError, the diagnostic now displays the name passed to qmc.qubit(...) instead of an internal ID. This makes it easier to relate the error location to the source code.

Other Changes