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

Qamomile v0.12.2では、アルゴリズム系のプリミティブを2つ追加しました — 任意状態を準備するためのMöttönen振幅エンコーディング、およびサンプルベースの部分対角化(QSCI)です。さらにコンパイラフロントエンドの使い勝手も改善しました: qmc.controlledqmc.ryのような組み込みゲート関数を、明示的な@qkernelラッパーなしに直接受け取れるようになりました。量子最適化には、BinaryModelommx.v1.Instanceに対して直接動くLocalSearchの後処理が加わりました。ドキュメントサイトは目的別の4セクション(tutorial/algorithm/usage/integration/)に再編され、タグベースのクロスディスカバリーUIを備えるとともに、新プリミティブを扱う2本のチュートリアルが追加されています。

pip install qamomile==0.12.2

新機能

Möttönen振幅エンコーディング

qamomile.circuit.algorithm.amplitude_encoding(qubits, amplitudes)は、Möttönen、Vartiainen、Bergholm、Salomaa(arXiv:quant-ph/0407010)のuniformly controlled rotation構成を用いて、任意の非ゼロ複素ベクトルaに振幅が比例するnqubit状態を|0⟩^⊗nから準備します(入力aはエンコード前に自動で正規化されます)。実数振幅(符号付き)は単一のRyカスケード(回転2^n - 1個、CNOT2^n - 2個)のみで完結し、複素振幅では2段目のRzカスケードが追加されます。複素入力で虚部が恒等的にゼロのものは、暗黙のうちに安価な実数経路へ落とされます(#383)。

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


@qmc.qkernel
def prepare_real() -> qmc.Vector[qmc.Bit]:
    q = qmc.qubit_array(2, "q")
    q = amplitude_encoding(q, [1.0, 2.0, 3.0, 4.0])
    return qmc.measure(q)


exe = QiskitTranspiler().transpile(prepare_real)

amplitude_encoding(q, amps)は、コンパイル時に値が確定するVector[Float]カーネルパラメータも受け取れます。

@qmc.qkernel
def prepare_via_binding(amps: qmc.Vector[qmc.Float]) -> qmc.Vector[qmc.Bit]:
    q = qmc.qubit_array(2, "q")
    q = amplitude_encoding(q, amps)
    return qmc.measure(q)


exe = QiskitTranspiler().transpile(
    prepare_via_binding, bindings={"amps": [1.0, 2.0, 3.0, 4.0]}
)

1つのコンパイル済み回路を、ランタイムに多数の振幅ベクトルへ再バインドしたいハイブリッド最適化ループでは、事前計算済みのMöttönen角度を渡すamplitude_encoding_from_angles(q, ry_angles, rz_angles=None)を使います。古典側の角度計算ヘルパーcompute_mottonen_amplitude_encoding_ry_anglescompute_mottonen_amplitude_encoding_rz_anglesqamomile.linalgから提供されており、振幅ベクトルを直接受け取ります。

import qamomile.circuit as qmc
from qamomile.circuit.algorithm import amplitude_encoding_from_angles
from qamomile.linalg import (
    compute_mottonen_amplitude_encoding_ry_angles,
    compute_mottonen_amplitude_encoding_rz_angles,
)
from qamomile.qiskit import QiskitTranspiler


@qmc.qkernel
def prepare_from_angles(
    ry_a: qmc.Vector[qmc.Float], rz_a: qmc.Vector[qmc.Float]
) -> qmc.Vector[qmc.Bit]:
    q = qmc.qubit_array(2, "q")
    q = amplitude_encoding_from_angles(q, ry_a, rz_a)
    return qmc.measure(q)


exe = QiskitTranspiler().transpile(
    prepare_from_angles, parameters=["ry_a", "rz_a"]
)

# ハイブリッドループの各反復で再バインド:
amps = [1 + 0j, 1j, -1 + 0j, -1j]
ry_angles = compute_mottonen_amplitude_encoding_ry_angles(amps).tolist()
rz_angles = compute_mottonen_amplitude_encoding_rz_angles(amps).tolist()

詳細はMöttönen振幅エンコーディングを参照してください。

サンプルベースの部分対角化(QSCI)

新しいqamomile.linalg.subspaceモジュールは、量子状態をZ基底で測定したビット文字列、あるいはX/Y/Zパウリ固有基底の混在による積状態のサンプル列を入力に取り、射影されたHamiltonianと(必要に応じて)重なり行列を組み立てて、得られた(一般化)固有値問題を古典側で解きます。これはQuantum Selected Configuration Interaction(QSCI; Kanno et al.、arXiv:2302.11320)や、関連するサンプルベースの変分手法のための基盤要素で、ノイズのある量子入力に対しても変分原理によってE_QSCI ≥ E_exactが保証されます(#376)。

qamomile.linalgからは3つの公開ヘルパーが利用できます。

import qamomile.observable as qm_o
from qamomile.linalg import solve_subspace

n = 4
H = qm_o.Hamiltonian(num_qubits=n)
for i in range(n - 1):
    H += qm_o.Z(i) * qm_o.Z(i + 1) * (-1.0)
for i in range(n):
    H += qm_o.X(i) * (-0.7)

samples = [
    (0, 0, 0, 0), (1, 1, 1, 1), (1, 0, 0, 0),
    (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1),
]
eigvals, eigvecs = solve_subspace(samples, H)
print(f"ground-state estimate: {eigvals[0]:+.6f}")

qubit0は計算基底indexの最下位ビット(LSB)に対応します。これはHamiltonian.to_numpy()およびHermitianMatrixの規約と一致します。Y固有状態の規約は|Y, 0⟩ = |+i⟩(つまりY |+i⟩ = +|+i⟩)で、Y測定をZ基底に写す基底回転パルスS† Hと整合しています。

詳細はQuantum Selected Configuration Interaction(QSCI)を参照してください。

qmc.controlledが組み込みゲート関数を受け取れる

qmc.controlledは従来@qmc.qkernelでラップしたcallableのみを受け取っていたため、制御をかけたいプリミティブごとに1行のボイラープレート的ラッパーを書く必要がありました。今回から、qmc.rxqmc.hqmc.cpなどの素のビルトインゲート関数をそのまま受け取れるようになっています — シグネチャを内省し、内部でラッパーを自動合成します。合成されたラッパーはcallableごとにキャッシュされるため、実コストがかかるのは最初の1回のみです(#374)。

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


controlled_ry = qmc.controlled(qmc.ry)


@qmc.qkernel
def controlled_ry_demo(angle: qmc.Float) -> qmc.Vector[qmc.Bit]:
    q = qmc.qubit_array(2, "q")
    q[0] = qmc.h(q[0])
    q[0], q[1] = controlled_ry(q[0], q[1], angle=angle)
    return qmc.measure(q)


exe = QiskitTranspiler().transpile(controlled_ry_demo, parameters=["angle"])

古典引数のキーワード(上記のangle=...)は、合成側で生成された名前ではなく、元になったゲート関数自身のパラメータ名です — qmc.rx / qmc.ry / qmc.rzangleqmc.pthetaという具合です。qmc.controlledは引き続き手書きの@qmc.qkernel callableも従来通り受け取れるため、複数のプリミティブをまとめて1つの制御ブロックにしたいケースなど、独自のロジックが必要なときに使えます。

LocalSearchBinaryModelに対して実行

qamomile.optimization.post_process.LocalSearchは、バイナリ最適化モデルに対して単一ビット反転で貪欲にエネルギーを最小化する局所探索です。BinaryModel(SPINでもBINARYでも、HUBOを含む任意次数で可)、またはommx.v1.Instanceを受け取ります。内部ではSPIN表現に変換して探索した後、呼び出し側の元のvartypeに戻して結果を返します。反転選択戦略はLocalSearchMethodで2種類が選べます: BEST(1ステップの最大エネルギー減少幅で選択)とFIRST(index順で最初に改善する反転を選択)です(#332)。

from qamomile.optimization.binary_model.model import BinaryModel
from qamomile.optimization.post_process import LocalSearch, LocalSearchMethod

model = BinaryModel.from_ising(
    linear={0: 0.5},
    quad={(0, 1): -1.0, (1, 2): -1.0},
)
ls = LocalSearch(model)
sample_set = ls.run(initial_state=[1, 1, -1], method=LocalSearchMethod.BEST)

BinaryModelから構築した場合、run()はモデル元来のvartypeで単一サンプルを持つBinarySampleSetを返します。ommx.v1.Instanceから構築した場合、run()は呼び出し側の元の(制約を保持した)instanceに対して評価されたommx.v1.Solutionを返すため、実行可能性や制約ごとの診断情報はOMMXのAPI経由で取得できます。Instanceから構築したLocalSearchに対しては、run()initial_stateommx.v1.Solutionを直接渡して他のソルバの出力をウォームスタートにも使えます。BinaryModelから構築したLocalSearchに同じ形で渡すと、Solutionを解釈するinstanceが無いためValueErrorになります。呼び出し側のinstanceが変更されることはありません: LocalSearchは構築時にディープコピーを取ってからto_hubo()を呼ぶため、HUBO化で導入されるスラック変数が外に漏れ出ることはありません。

内部的な変更

Vectorの長さ取得を公開API化したget_size

qamomile.circuit.frontend.handle.get_size(arr)が、Vector[Qubit] / Vector[Float]ハンドルのqubit数 / 要素数をトレース時に読み出すための公開ヘルパーになりました。先頭シェイプエントリがコンパイル時定数まで解決されている場合(qmc.qubit_array(N, ...)intリテラル指定、または下位のValueが定数化されたUIntハンドル — uint(literal)_create_bound_input、partial evaluationによって設定されたもの)、Pythonのintとして大きさを返します。先頭次元がまだ記号的に残っている場合はValueErrorを送出するため、未解決サイズを取り扱いたい呼び出し側は明示的にcatchする必要があります。これまではstdlib/qft内部のプライベートな_get_sizeとして存在していましたが、qftと新しいamplitude_encodingモジュールの双方から使われるようになったため、高レベルのアルゴリズム部品の作者は「このレジスタの大きさはいくつ?」を尋ねる正規の方法を持てるようになりました。

Why: MöttönenカスケードのDepthとQFTのスワップ配置の双方で、トレース時にレジスタサイズを読む必要があります。stdlib外にヘルパーを昇格させることでalgorithm/に同じ実装が二重に出現するのを避けつつ、外部のアルゴリズム作者にも、組み込みアルゴリズムが使っているのと同じプリミティブを提供できます。

バグ修正

ドキュメント

さらに詳しく