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

Qamomile v0.13.0では、SELECT操作とglobal phase操作を追加しました。さらに、qmc.controlでは、整数のcontrol_valueを指定できるようになりました。Qiskitと回路描画機能はoptionalになったため、引き続きQiskitを利用する場合はpip install "qamomile[qiskit]"を実行してください。

pip install qamomile==0.13.0

破壊的変更

新機能

構造を表す厳密な数学式

qmc.log2qmc.ceilは、量子カーネル内にtarget非依存の古典式を構築します。trace中の値をhost Pythonで評価せずに、register幅などのcompile-time構造を導出できます。具体値は即座にfoldし、未bindの入力はresource estimateで厳密な式として保持され、transpile時のbindingsで引数を指定すると解決されます。これらの式は回路構造を決めるため、引数はruntime backend parameterではありません。

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

@qmc.qkernel
def logarithmic_register(size: qmc.UInt) -> qmc.Vector[qmc.Bit]:
    width = qmc.ceil(qmc.log2(size))
    return qmc.measure(qmc.qubit_array(width, name="register"))

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

Semanticなqmc.selectと値指定のcontrol

qmc.select([U0, U1, ...])は、LSB-firstのindex registerが整数iを表すときに、共有するtarget量子ビットへcase Uiを適用するSELECT操作を作ります。index幅はcase数から推論できるほか、Pythonのintまたはqmc.UIntで指定できます。また、qmc.control(..., control_value=v)は、flattenした制御量子ビットが具体的なLSB-firstの値vと一致するときに対象の操作を実行する制御演算を作ります(#597)。

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

@qmc.qkernel
def select_example() -> tuple[qmc.Bit, qmc.Bit]:
    index = qmc.qubit(name="index")
    target = qmc.qubit(name="target")
    index = qmc.x(index)   # case 1を選択
    target = qmc.x(target)
    index, target = qmc.select([qmc.x, qmc.z])(index, target)
    return qmc.measure(index), qmc.measure(target)

transpiler = QiskitTranspiler()
executable = transpiler.transpile(select_example)

再利用可能な操作でglobal phaseを厳密に保持

qmc.global_phase(target, phase)targetを適用した後にexp(i * phase)を掛け、入れ子の量子カーネル呼び出し、control、inverse、power、structured control flow、Protobufのround tripを通して位相を明示的に保持します。単独のglobal phaseは観測できませんが、制御演算やSELECT演算の下では相対位相となり、干渉結果に現れます(#593#596)。

import math

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

@qmc.qkernel
def identity(qubit: qmc.Qubit) -> qmc.Qubit:
    return qubit

@qmc.qkernel
def phased_identity(qubit: qmc.Qubit) -> qmc.Qubit:
    return qmc.global_phase(identity, math.pi)(qubit)

@qmc.qkernel
def phase_kickback() -> tuple[qmc.Bit, qmc.Bit]:
    control = qmc.h(qmc.qubit(name="control"))
    target = qmc.h(qmc.qubit(name="target"))
    control, target = qmc.control(phased_identity)(control, target)
    control = qmc.h(control)
    target = qmc.h(target)
    return qmc.measure(control), qmc.measure(target)

transpiler = QiskitTranspiler()
executable = transpiler.transpile(phase_kickback)

ループをまたぐスカラー値と古典比較

qmc.rangeqmc.itemsの中で同じ型のUIntまたはFloatを再代入すると、ValidationErrorを送出せず、更新した値を次の反復へ渡せるようになりました。accumulatorや、後続のgate parameterまたはコンパイル時分岐に使う値を計算できます。実行時に反復回数が決まるwhileでの持ち越し、測定結果に基づくBitの持ち越し、型の異なる再代入、測定結果に依存する値を量子操作も含むループ内で持ち越す処理はサポートしていません。また、BitUIntFloatハンドルは、トレース時にPythonのbooleanへ変換されず、対応するPython比較演算子を使えるようになりました。数値の大小比較、等値・非等値、UIntFloatの混在比較、BitBitbool・0または1の整数・UIntとの等値比較をサポートします(#580#587)。

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

@qmc.qkernel
def accumulated_branch(n: qmc.UInt) -> qmc.Bit:
    qubit = qmc.qubit(name="qubit")
    total = 0
    for i in qmc.range(n):
        total = total + i
    if total >= 3:
        qubit = qmc.x(qubit)
    return qmc.measure(qubit)

transpiler = QiskitTranspiler()
executable = transpiler.transpile(accumulated_branch, bindings={"n": 3})

量子ビットを保持する射影とreset操作

qmc.project_xqmc.project_yqmc.project_zでは、射影後の量子ビットと古典Bitの両方を返します。このため、測定後の状態をそのまま使い続けられます。qmc.resetは同じ量子ビットを|0>にした新しいhandleを返し、qmc.measure_resetはZ基底の射影とresetをまとめて実行します。入力handleをconsumeし、返された量子ビットhandleへ状態を引き継ぐため、Qamomileのaffine ownership checkを維持します(#599)。

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

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

transpiler = QiskitTranspiler()
executable = transpiler.transpile(measure_and_reset)

汎用APIとMöttönen方式を指定するamplitude encoding

qmc.amplitude_encodingは、エンジンでの合成方法を規定せず、準備する状態を表すようになりました。qmc.mottonen_amplitude_encodingは明示的にMöttönenのアルゴリズムを利用します(#602)。

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

@qmc.qkernel
def prepare_bell() -> qmc.Vector[qmc.Bit]:
    qubits = qmc.qubit_array(2, name="qubits")
    qubits = qmc.mottonen_amplitude_encoding(
        qubits, [1.0, 0.0, 0.0, 1.0]
    )
    return qmc.measure(qubits)

transpiler = QiskitTranspiler()
executable = transpiler.transpile(prepare_bell)

Möttönen Amplitude Encodingを参照してください。

統一したcallable、opaque Oracle、拡張した標準ライブラリ

名前付きcomposite gateは通常の量子カーネルになり、直接呼び出し、control、inverse、serialization、visualization、本体からのresource estimateに1つのcall modelを使います。qmc.opaque(name, ...)qmc.Oracleは、本体を持たないcallableとしてtop-down設計に利用できます。target実装またはsubstitutionを指定するまで、固定またはcontext依存のresource costを保持できます。ResourceEstimateは論理width、gate category、depth、call・query数、assumption、説明用trace、estimate qualityを返し、未bindの入力はsymbolicな式として扱います。また、publicな標準ライブラリには、qmc.mcx、reversibleなripple-carry・modular算術、Grover search helper、Shor order findingを追加しました。状態準備helperも同じcallable modelを使うため、トランスパイルとresource estimateが同じ実行可能な本体を参照します(#571#586)。

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

@qmc.qkernel
def stdlib_mcx() -> tuple[qmc.Vector[qmc.Bit], qmc.Bit]:
    controls = qmc.x(qmc.qubit_array(3, name="controls"))
    target = qmc.qubit(name="target")
    controls, target = qmc.mcx(controls, target)
    return qmc.measure(controls), qmc.measure(target)

estimate = stdlib_mcx.estimate_resources()
transpiler = QiskitTranspiler()
executable = transpiler.transpile(stdlib_mcx)

Protobufによる量子カーネルのserialization

qamomile.circuit.serialization.serialize()は、staticで未bindの量子カーネルを決定的なProtobuf byte列として書き出します。public signature、階層的な本体、callable definition、default値、annotation、global phase操作、値の関係を保持します。deserialize()は、通常のtranspiler interfaceから新しいcompile-time bindingとruntime parameterを受け取れる量子カーネル相当のobjectを復元します。bind済みまたはlowering済みのartifact、runtime value、エンジンのobjectはformatに含まれません(#595#596)。

import qamomile.circuit as qmc
from qamomile.circuit.serialization import deserialize, serialize
from qamomile.qiskit import QiskitTranspiler

@qmc.qkernel
def serializable_flip() -> qmc.Bit:
    qubit = qmc.x(qmc.qubit(name="qubit"))
    return qmc.measure(qubit)

payload = serialize(serializable_flip)
restored = deserialize(payload)

transpiler = QiskitTranspiler()
executable = transpiler.transpile(restored)

内部的な変更

上記の機能は、target非依存の準備stage、統一したcallable graph、エンジン非依存の回路表現の上に構築されています。これらの境界により、すべてのtargetを同じ実行modelへ押し込めずに、回路SDKとprogram graph targetでQamomileのsemanticsを共有できます。

明示的なcompiler targetとエンジン非依存の回路lowering

QamomileCompiler.prepare()は、到達可能なcallable definition、structured control flow、call graph、古典ABIを保持するPreparedModuleを生成します。回路エンジンはnative materializationの前に、これを検証済みCircuitProgramへloweringします。新しいHugrTranspilerは準備済みsemanticsをHUGR packageへ直接トランスパイルします。新しいQurationTranspilerはPyQretを通してmaterializeし、Qurationのresource compilationを公開します。HUGRのdependencyはhugr extraからインストールできますが、現時点でPyQretは別途インストールが必要です(#586)。

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

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

prepared = QamomileCompiler().prepare(compiler_example)
transpiler = QiskitTranspiler()
executable = transpiler.transpile(compiler_example)

Why: targetがloweringを選ぶまでsemanticなcallとcontrol flowの構造を保持することで、core IRをエンジン非依存に保ち、HUGRではprogram graph構造を維持し、回路エンジンではSDK native objectを構築する前に1つの検証済み境界を共有できます。

Yieldによるbranch merge

IfOperationは、mergeする各resultをtrue branchとfalse branchの並列なyield valueとして記録するようになりました。従来のPhiOp pseudo-operationは削除し、iter_merges()add_merge()により、compiler pass、serialization、dependency analysis、classical executionで共通する検証済みのaccessor経路を提供します(#570#574)。

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

@qmc.qkernel
def measurement_branch_merge() -> tuple[qmc.Bit, qmc.Bit]:
    condition = qmc.measure(qmc.h(qmc.qubit(name="condition")))
    target = qmc.qubit(name="target")
    if condition:
        target = qmc.x(target)
    return condition, qmc.measure(target)

transpiler = QiskitTranspiler()
executable = transpiler.transpile(measurement_branch_merge)

Why: 上記のuser-facing featureとは独立した内部構造の変更です。branch resultを明示的なyieldで表すことで、IRをstructured control flowに合わせ、affineなquantum valueがoperandとして二重に数えられるのを防ぎ、compilerの各consumerでmerge semanticsを一貫させます。

バグ修正

ドキュメント

さらに詳しく