This module implements the Grover Adaptive Search (GAS) algorithm for Combinatorial Polynomial Binary Optimization (CPBO).
The quantum optimization algorithm iteratively applies Grover’s search to find the minimum of the function. The GAS algorithm is designed to efficiently find the optimal solution by adaptively adjusting the search space based on previous iterations’ results.
Overview¶
| Function | Description |
|---|---|
diffusion_op | Apply the Grover diffusion operator on the input register. |
grover_algorithm | Run repeated Grover iterations for the QUBO GAS circuit. |
qft_encoding | Encode a scalar coefficient as phase rotations in the QFT basis. |
zero_degree_qft_encoding | Apply an unconditional phase-encoding term. |
| Class | Description |
|---|---|
BinaryModel | |
ExecutableProgram | A fully compiled program ready for execution. |
GASConverter | Converter for Grover Adaptive Search (GAS). |
MathematicalProblemConverter | Base class for converters that compile a problem into a circuit. |
Transpiler | Base class for engine-specific transpilers. |
Functions¶
diffusion_op [source]¶
def diffusion_op(q_input: qmc.Vector[qmc.Qubit]) -> qmc.Vector[qmc.Qubit]Apply the Grover diffusion operator on the input register.
Implements the reflection 2|s><s| - I about the uniform superposition via the X^n C^{n-1}Z X^n circuit identity. A single-qubit register uses a bare Z instead, since that identity would degenerate into a controlled gate with no controls.
Parameters:
| Name | Type | Description |
|---|---|---|
q_input | qmc.Vector[qmc.Qubit] | Input register to reflect around the uniform superposition. |
Returns:
qmc.Vector[qmc.Qubit] — qmc.Vector[qmc.Qubit]: Updated input register.
grover_algorithm [source]¶
def grover_algorithm(
n: qmc.UInt,
m: qmc.UInt,
y: qmc.Float,
linear: qmc.Dict[qmc.UInt, qmc.Float],
quad: qmc.Dict[qmc.Tuple[qmc.UInt, qmc.UInt], qmc.Float],
iters: qmc.UInt = 1,
) -> tuple[qmc.Vector[qmc.Qubit], qmc.Vector[qmc.Qubit]]Run repeated Grover iterations for the QUBO GAS circuit.
Parameters:
| Name | Type | Description |
|---|---|---|
n | qmc.UInt | Number of input qubits. |
m | qmc.UInt | Number of output qubits. |
y | qmc.Float | Objective threshold offset encoded as a constant term. |
linear | qmc.Dict[qmc.UInt, qmc.Float] | Linear coefficients indexed by variable. |
quad | qmc.Dict[qmc.Tuple[qmc.UInt, qmc.UInt], qmc.Float] | Quadratic coefficients indexed by variable pairs. |
iters | qmc.UInt | Number of Grover iterations. |
Returns:
tuple[qmc.Vector[qmc.Qubit], qmc.Vector[qmc.Qubit]] — tuple[qmc.Vector[qmc.Qubit], qmc.Vector[qmc.Qubit]]: Output and input qubit registers after all iterations.
qft_encoding [source]¶
def qft_encoding(q: qmc.Vector[qmc.Qubit], coef: qmc.Float) -> qmc.Vector[qmc.Qubit]Encode a scalar coefficient as phase rotations in the QFT basis.
Parameters:
| Name | Type | Description |
|---|---|---|
q | qmc.Vector[qmc.Qubit] | Output register represented in the Fourier basis. |
coef | qmc.Float | The coefficient to encode as a phase. |
Returns:
qmc.Vector[qmc.Qubit] — qmc.Vector[qmc.Qubit]: The output register with the QFT encoding of coef.
zero_degree_qft_encoding [source]¶
def zero_degree_qft_encoding(
q_output: qmc.Vector[qmc.Qubit],
q_input: qmc.Vector[qmc.Qubit],
coef: qmc.Float,
) -> tuple[qmc.Vector[qmc.Qubit], qmc.Vector[qmc.Qubit]]Apply an unconditional phase-encoding term.
Parameters:
| Name | Type | Description |
|---|---|---|
q_output | qmc.Vector[qmc.Qubit] | Output register in the Fourier basis. |
q_input | qmc.Vector[qmc.Qubit] | Input register carried through unchanged. |
coef | qmc.Float | Coefficient of the constant term to encode. |
Returns:
tuple[qmc.Vector[qmc.Qubit], qmc.Vector[qmc.Qubit]] — tuple[qmc.Vector[qmc.Qubit], qmc.Vector[qmc.Qubit]]: Updated output and input registers.
Classes¶
BinaryModel [source]¶
class BinaryModel(Generic[VT])Constructor¶
def __init__(self, expr: BinaryExpr[VT]) -> NoneAttributes¶
coefficients: dict[tuple[int, ...], float] All coefficients as a single flat dictionary using sequential indices.constant: floathigher: dict[tuple[int, ...], float]index_new_to_originindex_origin_to_newlinear: dict[int, float]num_bits: intorderquad: dict[tuple[int, int], float]vartype: VT
Methods¶
calc_energy¶
def calc_energy(self, state: list[int]) -> floatCalculate the energy for a given variable assignment.
Parameters:
| Name | Type | Description |
|---|---|---|
state | list[int] | Variable values indexed by sequential (zero-origin) indices. For SPIN: values must be +1 or -1. For BINARY: values must be 0 or 1. |
Returns:
float — The energy value.
Raises:
ValueError— If state values are invalid for the vartype.
change_vartype¶
def change_vartype(self, vartype: VarType) -> 'BinaryModel'decode_from_sampleresult¶
def decode_from_sampleresult(self, result: SampleResult[list[int]]) -> BinarySampleSet[VT]Decode quantum measurement results into samples with energies.
Converts raw measurement bitstrings (0/1 from quantum hardware) into the model’s variable domain and calculates energies.
Parameters:
| Name | Type | Description |
|---|---|---|
result | SampleResult[list[int]] | Measurement results with sequential bit indices |
Returns:
BinarySampleSet[VT] — BinarySampleSet with samples in expression domain, using original indices
from_higher_ising¶
@classmethod
def from_higher_ising(
cls,
higher_ising: dict[tuple[int, ...], float],
constant: float = 0.0,
simplify: bool = False,
) -> 'BinaryModel'Create a SPIN BinaryModel from higher-order Ising coefficients.
Accepts Ising-style terms of arbitrary order (linear, quadratic, cubic,
quartic, and beyond) in a single coefficient dictionary. Duplicate
indices within a term are reduced using the identity s_i**2 = 1
for SPIN variables: each pair of repeated indices cancels to a
constant factor, so e.g. (0, 0, 2) becomes (2,) and
(0, 0, 1, 1, 2) becomes (2,). A warning is emitted whenever
such reduction occurs.
Parameters:
| Name | Type | Description |
|---|---|---|
higher_ising | dict[tuple[int, ...], float] | Higher-order Ising coefficients mapping index tuples to SPIN interaction strengths. Index tuples are sorted; repeated indices are reduced via s_i**2 = 1. Empty tuples (()) are accumulated into the constant term. |
constant | float | Constant offset term. Defaults to 0.0. |
simplify | bool | If True, remove near-zero coefficients after accumulation. Defaults to False. |
Returns:
'BinaryModel' — BinaryModel with SPIN vartype whose coefficients encode the
'BinaryModel' — supplied higher-order Ising terms.
Example:
>>> model = BinaryModel.from_higher_ising(
... {(0,): 1.0, (0, 1): -2.0, (0, 1, 2): 0.5},
... constant=0.25,
... )
>>> model.vartype
<VarType.SPIN: 'SPIN'>from_hubo¶
@classmethod
def from_hubo(
cls,
hubo: dict[tuple[int, ...], float],
constant: float = 0.0,
simplify: bool = False,
) -> 'BinaryModel'Create a BINARY BinaryModel from HUBO coefficients.
Parameters:
| Name | Type | Description |
|---|---|---|
hubo | dict[tuple[int, ...], float] | HUBO coefficients mapping index tuples to values. Index tuples are sorted and deduplicated. Duplicate indices in a single term (e.g., (0, 0, 2)) emit a warning and are normalized to unique indices ((0, 2)). Empty tuples (()) are accumulated into the constant term. |
constant | float | Constant offset term. |
simplify | bool | If True, remove near-zero coefficients. |
Returns:
'BinaryModel' — BinaryModel with BINARY vartype.
from_ising¶
@classmethod
def from_ising(
cls,
linear: dict[int, float],
quad: dict[tuple[int, int], float],
constant: float = 0.0,
simplify: bool = False,
) -> 'BinaryModel'from_qubo¶
@classmethod
def from_qubo(
cls,
qubo: dict[tuple[int, int], float],
constant: float = 0.0,
simplify: bool = False,
) -> 'BinaryModel'normalize_by_abs_max¶
def normalize_by_abs_max(self, replace: bool = False) -> BinaryModel[VT]Normalize the BinaryModel by its absolute maximum coefficient.
Returns:
BinaryModel[VT] — BinaryModel[VT]: The normalized binary model.
normalize_by_factor¶
def normalize_by_factor(self, factor: float, replace: bool = False) -> BinaryModel[VT]Normalize the BinaryModel by a given factor.
Parameters:
| Name | Type | Description |
|---|---|---|
factor | float | The normalization factor. |
Returns:
BinaryModel[VT] — BinaryModel[VT]: The normalized binary model.
normalize_by_rms¶
def normalize_by_rms(self, replace: bool = False) -> BinaryModel[VT]Normalize the BinaryModel by its root mean square.
Returns:
BinaryModel[VT] — BinaryModel[VT]: The normalized binary model.
ExecutableProgram [source]¶
class ExecutableProgram(Generic[T])A fully compiled program ready for execution.
Contains compiled quantum, classical, and expectation-value segments.
Use sample() for multi-shot execution or run() for single
execution.
Example:
executable = transpiler.compile(kernel)
# Sample: multiple shots, returns counts
job = executable.sample(executor, shots=1000)
result = job.result() # SampleResult with counts
# Run: single shot, returns typed result
job = executable.run(executor)
result = job.result() # Returns kernel's return typeConstructor¶
def __init__(
self,
plan: ProgramPlan | None = None,
compiled_quantum: list[CompiledQuantumSegment[T]] = list(),
compiled_classical: list[CompiledClassicalSegment] = list(),
compiled_expval: list[CompiledExpvalSegment] = list(),
output_values: list[ValueLike] = list(),
) -> NoneAttributes¶
compiled_classical: list[CompiledClassicalSegment]compiled_expval: list[CompiledExpvalSegment]compiled_quantum: list[CompiledQuantumSegment[T]]has_parameters: bool Check if this program has unbound parameters.output_values: list[ValueLike]parameter_names: list[str] Get list of parameter names that need binding.plan: ProgramPlan | Nonequantum_circuit: T Get the single quantum circuit.
Methods¶
get_circuits¶
def get_circuits(self) -> list[T]Get all quantum circuits in execution order.
get_first_circuit¶
def get_first_circuit(self) -> T | NoneGet the first quantum circuit, or None if no quantum segments.
restore¶
def restore(
self,
executor: QuantumExecutor[T],
snapshot: JobSnapshot,
bindings: dict[str, Any] | None = None,
) -> SampleJob[Any] | RunJob[Any] | ExpvalJobRestore saved executions with this program’s typed result ABI.
Snapshots retain provider identifiers, completed local raw values, and ordered execution groups. Legacy flat provider snapshots remain supported. Reuse the same compiled program and pass the original runtime bindings explicitly to reproduce classical pre- and post-processing. Credentials, arbitrary bindings, and Python callables are not saved. Restoration reconnects to remote jobs without resubmitting or waiting for results; local values need no provider restoration support.
Parameters:
| Name | Type | Description |
|---|---|---|
executor | QuantumExecutor[T] | Engine adapter configured with the provider credentials and target used by the original job. |
snapshot | JobSnapshot | Snapshot returned by the original public job’s snapshot() method. |
bindings | dict[str, Any] | None | Original runtime parameter bindings. Defaults to None for parameter-free programs. |
Returns:
SampleJob[Any] | RunJob[Any] | ExpvalJob — SampleJob[Any] | RunJob[Any] | ExpvalJob: Restored lazy job with
the same typed public result conversion as a new execution.
Raises:
ExecutionError— If the snapshot operation or execution shape does not match this executable program.NotImplementedError— If the executor cannot restore the referenced provider execution.ValueError— If required bindings are missing or invalid.
Example:
>>> original = executable.sample(executor, shots=1000)
>>> snapshot = original.snapshot()
>>> restored = executable.restore(executor, snapshot)
>>> restored.result()run¶
def run(
self,
executor: QuantumExecutor[T],
bindings: dict[str, Any] | None = None,
*,
estimation: EstimationAccuracy | None = None,
) -> RunJob[Any] | ExpvalJobSubmit one execution and return its lazy result job.
Parameters:
| Name | Type | Description |
|---|---|---|
executor | QuantumExecutor[T] | Engine-specific quantum executor. |
bindings | dict[str, Any] | None | Parameter bindings. Supports three formats: - Vector: {“gammas”: [0.1, 0.2], “betas”: [0.3, 0.4]} - Dict parameter: {“coeffs”: {0: 0.1, (0, 1): 0.2}}, decomposed per key onto the emitted parameters - Indexed: {“gammas[0]”: 0.1, “coeffs[(0, 1)]”: 0.2} |
estimation | EstimationAccuracy | None | Optional per-execution expectation accuracy policy. Defaults to the executor’s configured behavior. |
Returns:
RunJob[Any] | ExpvalJob — RunJob[Any] | ExpvalJob: A RunJob that resolves to the kernel’s
return type, or an ExpvalJob when the program contains an
expectation-value computation.
Raises:
ExecutionError— If no quantum circuit to executeValueError— If required parameters are missing
Example:
job = executable.run(executor, bindings={"gamma": [0.5]})
result = job.result()
print(result) # 0.25 (for QFixed) or (0, 1) (for bits)sample¶
def sample(
self,
executor: QuantumExecutor[T],
shots: int = 1024,
bindings: dict[str, Any] | None = None,
) -> SampleJob[Any]Submit a multi-shot execution and return its lazy job.
Parameters:
| Name | Type | Description |
|---|---|---|
executor | QuantumExecutor[T] | Engine-specific quantum executor. |
shots | int | Number of shots to run. |
bindings | dict[str, Any] | None | Parameter bindings. Supports three formats: - Vector: {“gammas”: [0.1, 0.2], “betas”: [0.3, 0.4]} - Dict parameter: {“coeffs”: {0: 0.1, (0, 1): 0.2}}, decomposed per key onto the emitted parameters - Indexed: {“gammas[0]”: 0.1, “coeffs[(0, 1)]”: 0.2} |
Returns:
SampleJob[Any] — SampleJob[Any]: A job that resolves to a SampleResult with the
per-bitstring counts.
Raises:
ExecutionError— If no quantum circuit to executeValueError— If required parameters are missing
Example:
job = executable.sample(executor, shots=1000, bindings={"gamma": [0.5]})
result = job.result()
print(result.results) # [(0.25, 500), (0.75, 500)]GASConverter [source]¶
class GASConverter(MathematicalProblemConverter)Converter for Grover Adaptive Search (GAS).
Encodes the BINARY-domain model normalized by the base converter, so that the Grover QFT-arithmetic circuit receives the correct QUBO coefficients (binary variables take values in {0, 1}, not ±1).
Methods¶
approximate_real_valued_model¶
@staticmethod
def approximate_real_valued_model(
binary_model: BinaryModel,
quantization_parameter: int | None = None,
) -> tuple[BinaryModel, float]Rescale and round all model coefficients to integers for QFT arithmetic.
Divides every coefficient (including the constant) by the maximum absolute
value to map them into [-1, 1], then multiplies by 2^(quantization_parameter - 1)
and rounds to the nearest integer. The resulting model has integer coefficients
that the Grover QFT circuit can encode exactly.
The scale relating the two models is returned alongside the model rather
than discarded: the quantized objective is scale × f(x), so any
threshold expressed in the original scale is only comparable against it
after being multiplied by scale.
Parameters:
| Name | Type | Description |
|---|---|---|
binary_model | BinaryModel | The original binary model with real-valued coefficients. |
quantization_parameter | int | None | Number of bits used for the fixed-point representation. 2^(quantization_parameter - 1) is the scale factor applied after normalisation. When None, the value is chosen automatically by _greedy_quantization_parameter. |
Returns:
tuple[BinaryModel, float] — tuple[BinaryModel, float]: A new binary model whose coefficients are
integers approximating the original up to the chosen precision,
and the scale factor s such that each returned coefficient
approximates s × its original counterpart. s is 1.0
when the model is returned unchanged.
get_cost_hamiltonian¶
def get_cost_hamiltonian(self) -> qm_o.HamiltonianRaise NotImplementedError because GAS is oracle-based and has no cost Hamiltonian.
GAS marks states via an oracle reflection rather than minimizing the
expectation value of a cost Hamiltonian. This method always raises so
that incorrect usage fails immediately rather than propagating a silent
None that would cause a confusing error later.
Returns:
qm_o.Hamiltonian — qm_o.Hamiltonian: Never returns; declared for base-class
qm_o.Hamiltonian — compatibility only.
Raises:
NotImplementedError— Always. GAS exposes no cost Hamiltonian.
required_output_bits¶
def required_output_bits(self, y: float = 0.0) -> intReturn the output-register width transpile() would pick for y.
Exposes the automatic sizing so callers can reserve the register
themselves — for drawing the circuit, estimating resources, or checking
a width before passing it as output_bits.
Reports the width for the current effective_model. On a freshly
built converter that is the un-quantized model, so for a model with
real-valued coefficients the answer grows once transpile() has
quantized them: call this again afterwards, or pass
approximate_real_coefficients=False.
Parameters:
| Name | Type | Description |
|---|---|---|
y | float | Oracle threshold in the model’s original (un-quantized) scale, exactly as it would be passed to transpile(). Defaults to 0.0. |
Returns:
int — Minimum number of output qubits for the current
effective_model and quantization_scale.
transpile¶
def transpile(
self,
transpiler: Transpiler,
*,
output_bits: int | None = None,
y: float,
num_iterations: int,
approximate_real_coefficients: bool = True,
quantization_parameter: int | None = None,
) -> ExecutableProgramTranspile the model into an executable Grover circuit.
Dispatches to the quadratic-only path, with build-in function, when no higher-order terms are present. Otherwise uses the HUBO path with the qkernel factory.
Parameters:
| Name | Type | Description |
|---|---|---|
transpiler | Transpiler | Backend transpiler to use. |
output_bits | int | None | Number of qubits in the arithmetic register holding f(x) - y. When None (default), the minimum sufficient size is computed automatically from the effective model and the threshold via required_output_bits. A manual value is rejected when it cannot represent the whole range of f(x) - y. |
y | float | Current best known objective value. The oracle marks all states x where f(x) < y. Pass the QUBO objective directly, in the model’s original scale — the sign convention and any quantization rescaling are handled internally. |
num_iterations | int | Number of Grover operator applications. |
approximate_real_coefficients | bool | When True (default) and the model has non-integer coefficients, quantize them to integers so the QFT arithmetic can encode them exactly. When False the real coefficients are encoded as-is. |
quantization_parameter | int | None | Bit width forwarded to approximate_real_valued_model when quantizing. None selects it automatically. Ignored unless quantization applies. |
Returns:
ExecutableProgram — The compiled circuit program.
Raises:
ValueError— Ifoutput_bitsis too small to representf(x) - yover the whole search space.
MathematicalProblemConverter [source]¶
class MathematicalProblemConverter(abc.ABC)Base class for converters that compile a problem into a circuit.
Constructor¶
def __init__(
self,
instance: ommx.v1.Instance | BinaryModel,
*,
uniform_penalty_weight: float | None = None,
penalty_weights: dict[int, float] | None = None,
) -> NoneInitialize a converter from an OMMX instance or binary model.
Parameters:
| Name | Type | Description |
|---|---|---|
instance | ommx.v1.Instance | BinaryModel | Optimization problem. |
uniform_penalty_weight | float | None | Uniform constraint penalty passed to OMMX. None delegates selection to OMMX. |
penalty_weights | dict[int, float] | None | Optional per-constraint penalty weights keyed by constraint ID. |
Methods¶
decode¶
def decode(self, samples: SampleResult[list[int]]) -> BinarySampleSet | ommx.v1.SampleSetDecode quantum measurement results.
The return type tracks the input that built this converter:
Built from an :class:
ommx.v1.Instance— returns an :class:ommx.v1.SampleSetevaluated against the original (un-penalized) instance, so feasibility, objective, and per-constraint violations are available through OMMX’s own API (.summary,.summary_with_constraints,.best_feasible,.feasible,.objectives).Built from a :class:
BinaryModel— returns a :class:BinarySampleSetwith samples in the model’s original vartype (BINARY 0/1 or SPIN ±1), energies, and shot counts.
Parameters:
| Name | Type | Description |
|---|---|---|
samples | SampleResult[list[int]] | Raw quantum measurement results from ExecutableProgram.sample(...).result(). |
Returns:
BinarySampleSet | ommx.v1.SampleSet — BinarySampleSet | ommx.v1.SampleSet: see method description.
See Also:
:meth:decode_to_binary_sampleset: always returns a
:class:BinarySampleSet. Use it when you need the
QUBO-domain (penalty-included) energy — e.g. to drive a
classical optimizer that must penalize infeasibility.
Example:
>>> # OMMX in → OMMX out
>>> converter = QAOAConverter(ommx_instance)
>>> exe = converter.transpile(QiskitTranspiler(), p=2)
>>> result = exe.sample(QiskitTranspiler().executor(),
... shots=1024,
... bindings={"gammas": gs, "betas": bs}).result()
>>> sample_set = converter.decode(result)
>>> sample_set.best_feasible.objectivedecode_to_binary_sampleset¶
def decode_to_binary_sampleset(self, samples: SampleResult[list[int]]) -> BinarySampleSetDecode samples into a :class:BinarySampleSet.
Always returns a :class:BinarySampleSet, regardless of whether
this converter was constructed with an :class:ommx.v1.Instance
or a :class:BinaryModel. Use this when you need:
The QUBO-domain
energy(penalty-included), e.g. as the cost driving a classical optimizer like COBYLA — :meth:decodeon OMMX-backed converters returns the un-penalized OMMX objective which won’t penalize infeasibility.The per-state
samples/num_occurrences/vartypeviews from :class:BinarySampleSet.
For most usage — feasibility, original-objective evaluation,
per-constraint diagnostics — prefer the polymorphic
:meth:decode, which returns an :class:ommx.v1.SampleSet for
OMMX-backed converters.
Parameters:
| Name | Type | Description |
|---|---|---|
samples | SampleResult[list[int]] | Raw quantum measurement results from ExecutableProgram.sample(...).result(). |
Returns:
BinarySampleSet — keyed by the SPIN model’s original variable
BinarySampleSet — indices (the QUBO variable IDs for OMMX-backed converters)
BinarySampleSet — in the converter’s original_vartype — BINARY for
BinarySampleSet — OMMX-backed converters, the :class:BinaryModel’s declared
BinarySampleSet — vartype otherwise.
get_cost_hamiltonian¶
def get_cost_hamiltonian(self) -> qm_o.HamiltonianConstruct the cost Hamiltonian.
Subclasses must implement this method to build the appropriate
Hamiltonian for their specific algorithm (e.g., Pauli-Z for QAOA,
QRAC-encoded for QRAO). Oracle-based converters that do not use a cost
Hamiltonian (e.g., GASConverter) should raise NotImplementedError.
Returns:
qm_o.Hamiltonian — qm_o.Hamiltonian: The cost Hamiltonian.
Raises:
NotImplementedError— If the converter does not expose a cost Hamiltonian.
Transpiler [source]¶
class Transpiler(ABC, Generic[T])Base class for engine-specific transpilers.
Provides the full compilation pipeline from qkernel-like frontend objects to executable programs.
Example:
>>> from qamomile.circuit.transpiler import TranspilerConfig
>>> from qamomile.qiskit import QiskitTranspiler
>>> transpiler = QiskitTranspiler()
>>> executable = transpiler.transpile(kernel, bindings={"theta": 0.5})
>>> circuit = executable.get_first_circuit()
>>> config = TranspilerConfig.with_strategies({"qft": "approximate_k2"})
>>> transpiler.set_config(config)Attributes¶
MAX_UNROLL_DEPTH: intconfig: TranspilerConfig Get the transpiler configuration.
Methods¶
affine_validate¶
def affine_validate(self, block: Block) -> BlockPass 1.5: Validate affine type semantics.
This is a safety net to catch affine type violations that may have bypassed frontend checks. Validates that quantum values are used at most once.
analyze¶
def analyze(self, block: Block) -> BlockPass 2: Validate and analyze dependencies.
array_bounds_check¶
def array_bounds_check(self, block: Block) -> BlockPass 1.85: Reject reachable accesses outside resolved array bounds.
Runs after :meth:partial_eval so binding-dependent view extents and
indices are concrete where possible, and before declarative slice
operations are stripped. Statically zero-trip loop bodies are skipped
because their element accesses are unreachable.
Parameters:
| Name | Type | Description |
|---|---|---|
block | Block | Post-fold affine or hierarchical block to validate. |
Returns:
Block — The input block unchanged after successful validation.
Raises:
ValidationError— If a reachable constant element index is outside a resolved root-array or view-local extent.
classical_lowering¶
def classical_lowering(self, block: Block) -> BlockPass 2.25: Lower measurement-derived classical ops.
Identifies CompOp / CondOp / NotOp / BinOp
instances whose operand dataflow traces back to a measurement and
rewrites them to RuntimeClassicalExpr. Compile-time-foldable
and emit-time-foldable (loop-bound, parameter-bound) classical
ops are left unchanged.
Runs after analyze so the measurement-taint analysis has the
full dependency graph available, and before
validate_symbolic_shapes / plan / emit so downstream
passes can rely on the cleaner IR (in particular: future
segmentation work can dispatch on RuntimeClassicalExpr type
instead of the BitType-only heuristic).
constant_fold¶
def constant_fold(self, block: Block, bindings: dict[str, Any] | None = None) -> BlockPass 1.5: Fold constant expressions.
Evaluates BinOp operations when all operands are constants
or bound parameters. This prevents quantum segment splitting
from parametric expressions like phase * 2.
emit¶
def emit(
self,
separated: ProgramPlan,
bindings: dict[str, Any] | None = None,
parameters: list[str] | None = None,
) -> ExecutableProgram[T]Pass 4: Generate engine-specific code.
Parameters:
| Name | Type | Description |
|---|---|---|
separated | ProgramPlan | The separated program to emit |
bindings | dict[str, Any] | None | Parameter values to bind at compile time |
parameters | list[str] | None | Parameter names to preserve as engine parameters |
Raises:
ValueError— If a name appears in bothbindingsandparameters. This check also runs intranspileandto_block/build, butemitis a public step-by-step entry point that bypasses those, so the guard is repeated here to prevent a name from being silently baked in (its runtime parameter dropped) when the step-by-step API is driven directly.
executor¶
def executor(self, **kwargs: Any = {}) -> QuantumExecutor[T]Create a quantum executor for this engine.
inline¶
def inline(self, block: Block) -> BlockPass 1: Inline all inline-policy callable invocations.
lower_compile_time_ifs¶
def lower_compile_time_ifs(self, block: Block, bindings: dict[str, Any] | None = None) -> BlockPass 1.75: Lower compile-time resolvable IfOperations.
Evaluates IfOperation conditions (including expression-derived conditions via CompOp/CondOp/NotOp) and replaces resolved ones with selected-branch operations. Merge outputs are substituted with selected-branch values throughout the block.
This prevents SegmentationPass from seeing classical-only compile-time IfOperations that would otherwise split quantum segments.
partial_eval¶
def partial_eval(self, block: Block, bindings: dict[str, Any] | None = None) -> BlockPass 1.75: Fold constants and lower compile-time control flow.
plan¶
def plan(self, block: Block) -> ProgramPlanPass 3: Lower and split into a program plan.
Validates C→Q→C pattern with single quantum segment.
plan_circuit¶
def plan_circuit(
self,
prepared: PreparedModule,
bindings: dict[str, Any] | None = None,
) -> ProgramPlanLower a prepared semantic module into the circuit execution model.
This is the destructive circuit-family path: inline-policy calls are
flattened, compile-time structure is evaluated, affine and borrow
invariants are checked, measurement-dependent classical expressions
are classified, and the result is segmented into C-to-Q-to-C steps.
Program-graph targets must compile :class:PreparedModule directly
instead of invoking this method.
Parameters:
| Name | Type | Description |
|---|---|---|
prepared | PreparedModule | Hierarchical semantic program returned by :meth:prepare. |
bindings | dict[str, Any] | None | Compile-time bindings used for recursion unrolling and partial evaluation. Defaults to None. |
Returns:
ProgramPlan — Circuit-family host-orchestrated execution plan.
Raises:
QamomileCompileError— If validation, partial evaluation, or segmentation rejects the program.
prepare¶
def prepare(
self,
kernel: QKernelLike,
bindings: dict[str, Any] | None = None,
parameters: list[str] | None = None,
) -> PreparedModulePrepare a qkernel for target-specific planning and lowering.
This phase preserves callable boundaries. It performs tracing, entrypoint validation, configured substitutions, and parameter-shape resolution, then collects the reachable callable graph into a program-level semantic view.
Parameters:
| Name | Type | Description |
|---|---|---|
kernel | QKernelLike | QKernel or qkernel-like frontend object to prepare as a top-level entrypoint. |
bindings | dict[str, Any] | None | Compile-time values used while tracing and resolving parameter shapes. Defaults to None. |
parameters | list[str] | None | Argument names preserved as runtime parameters. Defaults to None. |
Returns:
PreparedModule — Hierarchical entrypoint, reachable callables,
call graph, and public ABI.
Raises:
ValueError— If a name appears in bothbindingsandparameters.EntrypointValidationError— If the top-level kernel uses quantum inputs or outputs.
resolve_parameter_shapes¶
def resolve_parameter_shapes(self, block: Block, bindings: dict[str, Any] | None = None) -> BlockPass 0.75: Resolve symbolic Vector parameter shape dims.
Qamomile circuits are compile-time fixed-structure. Parameter
Vector[Float] / Vector[UInt] inputs carry symbolic
{name}_dim{i} shape Values so frontend code like
arr.shape[0] returns a usable handle. This pass looks at
bindings and, for every parameter array that has a concrete
binding, substitutes those symbolic dims with constants so that
downstream loop-bound resolution sees fixed lengths.
Parameters without a concrete binding are left as-is; their symbolic dims are harmless as long as no compile-time structure decision depends on them (the library QAOA pattern).
set_config¶
def set_config(self, config: TranspilerConfig) -> NoneSet the transpiler configuration.
Parameters:
| Name | Type | Description |
|---|---|---|
config | TranspilerConfig | Transpiler configuration to use |
slice_borrow_check¶
def slice_borrow_check(self, block: Block) -> BlockPass 1.9: Post-fold slice-view linearity checker.
Runs after :meth:partial_eval has resolved slice bounds to
concrete values. Catches the slice-view linearity violations
that the trace-time frontend check cannot detect on its own —
specifically, slices whose bounds were symbolic at trace
time (so the frontend bulk-borrow tracker had to skip them)
and aliasing scenarios that only become visible once those
bounds are folded to constants:
A view whose newly-concrete coverage overlaps another live view of the same root parent.
A view whose newly-concrete coverage hits a slot that was consumed by a destructive operation earlier in the block.
Slice ownership changes that cannot be represented safely across control-flow boundaries.
Creating a direct element borrow (q[i]) emits no IR operation,
so this pass cannot observe the borrow site itself. Later uses of
that element do appear as operation operands and are checked for
conflicts with live slice views. Trace-time validation in
:func:qamomile.circuit.frontend.func_to_block._validate_returned_arrays
covers unreturned direct-element borrows that have no observable
operand use.
The pass is a pass-through for the IR — it only raises on violations and leaves the block unchanged on success.
Parameters:
| Name | Type | Description |
|---|---|---|
block | Block | Post-fold affine or hierarchical block to validate. |
Returns:
Block — The input block unchanged after successful validation.
Raises:
QubitBorrowConflictError— If live slice ownership conflicts with another view or direct access.QubitConsumedError— If a slice or operand accesses a slot already destroyed by a destructive operation.ValidationError— If the block kind is invalid or ownership cannot be propagated safely through control flow.
strip_slice_ops¶
def strip_slice_ops(self, block: Block) -> BlockPass 1.95: Remove SliceArrayOperation nodes from the block.
PartialEvaluationPass keeps these declarative ops through
constant folding so :meth:slice_borrow_check can use them
as view-declaration markers. Once the linearity check has run,
segmentation and downstream passes expect a classical-op-free
quantum stream — this pass performs that cleanup.
substitute¶
def substitute(self, block: Block) -> BlockPass 0.5: Apply substitutions (optional).
This pass rewrites inline callable targets and sets strategy names on boxed InvokeOperations based on config.
Parameters:
| Name | Type | Description |
|---|---|---|
block | Block | Block to transform |
Returns:
Block — Block with substitutions applied
to_block¶
def to_block(
self,
kernel: QKernelLike,
bindings: dict[str, Any] | None = None,
parameters: list[str] | None = None,
) -> BlockConvert a qkernel-like frontend object to a Block.
Parameters:
| Name | Type | Description |
|---|---|---|
kernel | QKernelLike | QKernel or qkernel-like frontend object to convert. |
bindings | dict[str, Any] | None | Concrete values to bind at trace time, including values used to resolve array shapes. |
parameters | list[str] | None | Names to keep as unbound runtime parameters. |
Returns:
Block — Hierarchical block for the frontend object.
Raises:
ValueError— If a name appears in bothbindingsandparameters(propagated fromkernel.build), violating the bindings/parameters disjointness rule.
Always uses kernel.build() so Python defaults, required arguments,
runtime parameters, and array shapes follow one validated entry path.
to_circuit¶
def to_circuit(self, kernel: QKernelLike, bindings: dict[str, Any] | None = None) -> TCompile and extract just the quantum circuit.
This is a convenience method for when you just want the engine circuit without the full executable.
Parameters:
| Name | Type | Description |
|---|---|---|
kernel | QKernelLike | QKernel or qkernel-like frontend object to compile. |
bindings | dict[str, Any] | None | Parameter values to bind. |
Returns:
T — Engine-specific quantum circuit.
transpile¶
def transpile(
self,
kernel: QKernelLike,
bindings: dict[str, Any] | None = None,
parameters: list[str] | None = None,
) -> ExecutableProgram[T]Full compilation pipeline from a qkernel-like object to executable.
Parameters:
| Name | Type | Description |
|---|---|---|
kernel | QKernelLike | QKernel or qkernel-like frontend object to compile. |
bindings | dict[str, Any] | None | Parameter values to bind (also resolves array shapes). Names in bindings and parameters must be disjoint — a name is either compile-time bound or runtime symbolic, never both. |
parameters | list[str] | None | Parameter names to preserve as engine parameters. Scalars/arrays of float/int/UInt are supported, plus Dict[K, Float]: each constant-key subscript lookup (d[key]) becomes one engine parameter named "d[<key>]", and the execution-time binding bindings={"d": {...}} is decomposed per key onto those parameters. A Dict runtime parameter is recorded in Block.param_slots as a slot whose type is a DictType (compile-time-bound Dicts and Tuple arguments stay out of the slot manifest); its emitted per-key parameters are visible via ExecutableProgram.parameter_names. |
Returns:
ExecutableProgram[T] — ExecutableProgram[T]: Executable wrapping the engine circuit
and the parameter metadata needed to re-bind runtime
parameters, ready for execution.
Raises:
ValueError— If a name appears in bothbindingsandparameters. A name being in both is ambiguous (placeholder value vs runtime symbol) and used to silently miscompile control-flow predicates that depended on parameter-array elements; rejecting the overlap up front keeps the contract unambiguous.QamomileCompileError— If compilation fails (validation, dependency errors)
Pipeline:
prepare: Trace and validate the entrypoint, apply configured substitutions, resolve parameter shapes, and preserve the reachable callable graph.
plan_circuit: Inline inline-policy calls, unroll recursion, validate affine and borrow rules, partially evaluate compile-time structure, analyze dependencies, and segment the program into the host-orchestrated C-to-Q-to-C model.
lower: Convert each quantum segment to immutable, engine-neutral
CircuitProgramIR.legalize: Select native intrinsics and Pauli-evolution realizations from target capabilities and compilation policy.
verify: Prove circuit structure and target legality before constructing engine objects.
materialize: Convert the legalized circuit IR to engine-native artifacts and preserve the executable ABI.
unroll_recursion¶
def unroll_recursion(self, block: Block, bindings: dict[str, Any] | None = None) -> BlockFixed-point loop of inline and branch lowering for recursion.
Each iteration unrolls one layer of self-referential inline
callable invocation and then lowers its compile-time base-case
IfOperation. Loop-carried Bit conditions remain visible until the
final validation pass so first-iteration constants cannot erase a real
backedge read. Terminates when no
inline callable invocation remains (success), when every residual call
is trapped inside an operation-owned block whose recursive callable
contract is unsupported (control / inverse / select over a recursive
kernel — raises a targeted error, see below), or when
MAX_UNROLL_DEPTH is reached (genuinely non-terminating top-level
recursion — raises).
Parameters:
| Name | Type | Description |
|---|---|---|
block | Block | The block to unroll. May be HIERARCHICAL (still containing self-referential callable invocations) or already AFFINE (returned unchanged). |
bindings | dict[str, Any] | None | Compile-time bindings used by condition lowering to select the base case. Defaults to None, meaning no bindings are applied. |
Returns:
Block — The fully unrolled, AFFINE block once no
inline callable invocation remains. Returned unchanged when the
input already has no calls.
Raises:
FrontendTransformError— If every remaining inline callable invocation is trapped inside aControlledUOperation.block, anInverseBlockOperationblock, or aSelectOperation.case_blocksentry (a self-recursive kernel was passed toqmc.control,qmc.inverse, orqmc.select), or if a genuinely non-terminating top-level recursion does not converge withinMAX_UNROLL_DEPTHiterations. The two cases carry distinct, cause-specific messages.
validate_symbolic_shapes¶
def validate_symbolic_shapes(self, block: Block) -> BlockPass 2.5: Reject unresolvable ForOperation loop bounds.
Runs after analyze so dependency info is complete. Raises
QamomileCompileError with an actionable message when a
gamma_dim0-style symbolic Value reaches a ForOperation
bound without being folded to a constant by
ParameterShapeResolutionPass, or when a loop bound depends
(directly or through classical arithmetic) on a runtime
parameter — loop bounds are compile-time structure and must be
provided via bindings, not parameters.
Parameters:
| Name | Type | Description |
|---|---|---|
block | Block | The analyzed block to validate. |
Returns:
Block — block, unchanged, when validation succeeds.
Raises:
QamomileCompileError— If a loop bound is an unresolved parameter shape dim or depends on a runtime parameter.