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

Qamomile v0.14.0 adds LCU block encodings for Pauli, Ising-Z, and periodic-shift operators, together with qmc.qsvt for quantum singular value transformation (QSVT). It also adds quantum arithmetic circuits such as constant addition and rewrites Shor order finding as an iterative algorithm that uses fewer logical qubits. The new qmc.ekera_hastad_factoring API constructs the quantum part of Ekerå–Håstad factoring.

pip install qamomile==0.14.0

Breaking Changes

New Features

LCU block encodings and QSVT

qamomile.linalg now provides PauliLCU for general complex square matrices whose dimensions are powers of two, and PeriodicShiftLCU for one- or multidimensional periodic-shift operators. The corresponding block encoding APIs are qmc.pauli_lcu_block_encoding and qmc.periodic_shift_lcu_block_encoding. This release also adds APIs for Ising-Z, identity, and block encodings assembled from existing block encodings. Each API returns an instance containing its normalization, signal-register width, system-register width, and a reusable unitary qkernel.

qmc.qsvt applies a caller-supplied projector-phase sequence to any LCUBlockEncoding. Phase values may be fixed through transpile-time bindings. To retain the phase sequence as a runtime parameter, fix phase_count through bindings and specify parameters=["phases"]. Phase synthesis for a desired polynomial and conversion from other QSP conventions are not provided, so callers must prepare the phases in advance (#598, #608, #611, #618).

import numpy as np

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

@qmc.qkernel
def transform(
    encoding: qmc.LCUBlockEncoding,
    phases: qmc.Vector[qmc.Float],
) -> tuple[qmc.Vector[qmc.Bit], qmc.Vector[qmc.Bit]]:
    signal = qmc.qubit_array(encoding.num_signal_qubits, "signal")
    system = qmc.qubit_array(encoding.num_system_qubits, "system")
    signal, system = qmc.qsvt(signal, system, phases, encoding)
    return qmc.measure(signal), qmc.measure(system)

matrix = np.array([[1.0, 0.0], [0.0, -1.0]], dtype=complex)
block_encoding = qmc.pauli_lcu_block_encoding(PauliLCU.from_matrix(matrix))

transpiler = QiskitTranspiler()
executable = transpiler.transpile(
    transform,
    bindings={
        "encoding": block_encoding,
        "phases": [0.0, 0.0],
    },
)

Arithmetic and lower-width factoring circuits

The standard library adds constant addition, controlled constant addition, constant modular addition, and a function that uses a quantum register as an address to look up a table value and XOR it into another quantum register. The existing qmc.shor_order_finding has also been updated to use windowed modular multiplication.

qmc.ekera_hastad_factoring constructs the quantum part of Ekerå–Håstad factoring for a product of two primes with similar bit lengths. It returns measured bits for the classical computation that recovers the factors, rather than returning the factors themselves. The circuit reduces the number of logical qubits used at once by reusing qubits after measurement and reset. Classical post-processing that recovers the factors from the measurements is not included (#613).

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

order_finding = qmc.shor_order_finding(
    base=2,
    modulus=15,
    precision=2,
)
short_dlp = qmc.ekera_hastad_factoring(
    generator=2,
    modulus=21,
    window_size=1,
)

transpiler = QiskitTranspiler()
order_executable = transpiler.transpile(order_finding)
short_dlp_executable = transpiler.transpile(short_dlp)

See Tutorial 05 — Resource Estimation.

qmc.struct, fixed-length Bit arrays, and register-width calculations

The qmc.struct decorator makes helper-based implementations of large algorithms easier to organize. It cannot be used as a qkernel argument or return value. qmc.bit_array creates a zero-initialized, fixed-length one-dimensional Vector[Bit], allowing measurement results to be stored by index and returned as one value (#613, #623).

The new classical-value operations qmc.log2 and qmc.ceil are evaluated immediately when their inputs are known. When inputs are unresolved, the operations remain symbolic in resource estimates and must be supplied through transpile-time bindings before the circuit is generated (#615).

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

@qmc.struct
class Workspace:
    qubits: qmc.Vector[qmc.Qubit]

@qmc.qkernel
def collect_bits(size: qmc.UInt) -> qmc.Vector[qmc.Bit]:
    width = qmc.ceil(qmc.log2(size))
    workspace = Workspace(qmc.qubit_array(width, "workspace"))
    measured = qmc.measure(workspace.qubits)
    output = qmc.bit_array(width, name="output")
    for index in qmc.range(width):
        output[index] = measured[index]
    return output

transpiler = QiskitTranspiler()
executable = transpiler.transpile(
    collect_bits,
    bindings={"size": 9},
)

Internal Changes

First-class qkernel effects

QKernel.effects reports a KernelEffect flag set containing MEASUREMENT, RESET, and FEED_FORWARD; KernelEffect.NONE denotes a unitary body. Effects propagate through nested callable definitions and are reconstructed after serialization (#613).

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

@qmc.qkernel
def reset_and_read() -> tuple[qmc.Bit, qmc.Bit]:
    qubit = qmc.x(qmc.qubit("qubit"))
    qubit, before = qmc.measure_reset(qubit)
    after = qmc.measure(qubit)
    return before, after

effects = reset_and_read.effects
transpiler = QiskitTranspiler()
executable = transpiler.transpile(reset_and_read)

Explicit region dataflow

Structured if, for, and while regions now record their captures, carried values, arguments, and yields explicitly. These interfaces are preserved through Protobuf serialization and checked before lowering (#616).

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

@qmc.qkernel
def fill_register(size: qmc.UInt) -> qmc.Vector[qmc.Bit]:
    qubits = qmc.qubit_array(size, "qubits")
    for index in qmc.range(size):
        qubits[index] = qmc.x(qubits[index])
    return qmc.measure(qubits)

transpiler = QiskitTranspiler()
executable = transpiler.transpile(
    fill_register,
    bindings={"size": 3},
)

Bug Fixes

Documentation

Learn More