Qamomile v0.12.2では、アルゴリズム系のプリミティブを2つ追加しました — 任意状態を準備するためのMöttönen振幅エンコーディング、およびサンプルベースの部分対角化(QSCI)です。さらにコンパイラフロントエンドの使い勝手も改善しました: qmc.controlledがqmc.ryのような組み込みゲート関数を、明示的な@qkernelラッパーなしに直接受け取れるようになりました。量子最適化には、BinaryModelやommx.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_anglesとcompute_mottonen_amplitude_encoding_rz_anglesはqamomile.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つの公開ヘルパーが利用できます。
subspace_hamiltonian(samples, hamiltonian)— Z基底のファストパス。XORとパリティのベクトル化計算のみで行列積は使いません。H_subだけを返します — 重なり行列は単位行列とみなしており、これは渡されたビット列がすべてpairwise distinctな場合に成立します。重複しうる場合は事前に重複排除するか、下の一般化経路を使ってください。generalized_subspace_matrices(samples, bases, hamiltonian)— 非直交な積状態基底(X/Y/Zの混在)に対して(H_sub, S_sub)を返します。Z基底のサンプルに重複が含まれうる場合の入り口としても適切です。solve_subspace(samples, hamiltonian, *, bases=None)— 便利ラッパー。bases=NoneではZ基底のファストパスでH_subを組み立て、それに対して標準のHermitian固有値分解(np.linalg.eigh)を実行します(つまりサンプルがdistinctであることが前提)。basesを渡した場合は一般化問題H_sub v = λ S_sub vを解き、重なり行列の固有値分解で白色化したうえで、overlap_tolで制御されるランク落ちフォールバックも自動で適用されます。
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.rx、qmc.h、qmc.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.rzはangle、qmc.pはthetaという具合です。qmc.controlledは引き続き手書きの@qmc.qkernel callableも従来通り受け取れるため、複数のプリミティブをまとめて1つの制御ブロックにしたいケースなど、独自のロジックが必要なときに使えます。
LocalSearchをBinaryModelに対して実行¶
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_stateにommx.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/に同じ実装が二重に出現するのを避けつつ、外部のアルゴリズム作者にも、組み込みアルゴリズムが使っているのと同じプリミティブを提供できます。
バグ修正¶
Vectorブロードキャストゲートがアフィン型ルールに従うようになりました(#387)。Vector[Qubit]に対するqmc.h(qs)(および他11個の単一qubitブロードキャストゲート)は、これまで戻り値を破棄すると入力ハンドルが再利用可能なままになっていました — スカラー版qmc.h(q)が正しくQubitConsumedErrorを出すのとは挙動が非対称でした。今回からブロードキャスト経路でも入力ハンドルは即座に消費されるようになり、_measure_vector_qubitやpauli_evolveの挙動と揃いました。出力されるIRは変わりません — 塞いだのは型エラー側の穴だけです。可視化: 空の
DictにバインドされたForItemsが折りたたみボックスとして描かれなくなりました(#384)。ForItemsが{}にバインドされたDictを反復している場合、analyzerは空のバインディングを「未バインド」と見なしてfold_loops=Falseでも折りたたみループのボックスを描いていました。空だけれどバインド済みのケースは、ゼロ反復のアンフォールド(VSkip)に折り畳まれるようになりました。可視化: ループ変数を参照するBinOpのオペランドが、表示名のまま漏れなくなりました(#384)。
ForItemsの本体内でgamma * JijのようなBinOp(ここでJijはループ変数の束縛エントリ)があると、これまでJijが生の表示名のままレンダリングされていました。analyzerはBinOpのオペランドを書式化する際にイテレーションごとの_loop_<name>値を参照するようになり、ラベルは0.5*gammaのように表示され、1*gammaは既存のBinOp簡略化器でgammaに縮約されます。
ドキュメント¶
ドキュメントサイトを目的別の4セクションに再編しました(#348)。従来の
tutorial/+optimization/+vqa/+collaboration/という分割は、tutorial/(コンパイラのウォークスルーのみ)、algorithm/(具体的なアルゴリズム例 — QAOAバリアント、VQE、誤り訂正、Hamiltonianシミュレーション、振幅エンコーディング、QSCI)、usage/(モジュールごとのhow-to)、integration/(外部プラットフォームのノート)に置き換わりました。各記事は冒頭フロントマターにtags:を持ち(例:[algorithm, encoding, primitive])、タグごとのページや「Browse by tag」のチップ群は、gitignoreされたdocs/_build_src/にビルド時に生成されます — コミットされるソースツリーは綺麗なままです。タグの語彙はALLOWED_TAGS(tests/docs/test_tag_whitelist.pyでCI強制)で小さく管理されます。algorithm/へ移動した既存チュートリアル:hamiltonian_simulation、quantum_error_correction、steane_code、qaoa_graph_partition、qaoa_maxcut、vqe_for_hydrogen。残りのチュートリアル番号は詰められました: 旧Tutorial 08(Hermitian分解)はTutorial 07、旧Tutorial 09(コンパイル&トランスパイル)はTutorial 08になりました。optimization/binary_modelはusage/binary_model、collaboration/qbraid_executorはintegration/qbraid_executorに移動しています。新しいMöttönen振幅エンコーディングチュートリアル — 4つの入力モード、ゲート数の検証、期待値の解析チェックを扱います。
新しいQuantum Selected Configuration Interaction(QSCI)チュートリアル — 4qubit横磁場Isingモデルで、VQEウォームアップ+Z基底サンプリング+古典側部分空間対角化までを通します。
QAOAグラフ分割チュートリアルに「QAOA回路の可視化」セクションが加わり、
MatplotlibDrawer(block).draw(fold_loops=False)でqaoa_stateの中のsuperposition_vector、ising_cost、x_mixerをインライン展開して描画します(#384)。古い
.ipynbの実行結果を、QSCI、06_reuse_patterns、hamiltonian_simulation、08_compilation_and_transpilation(JA)で再生成しました。QSCIチュートリアルはmystmdの{cite:p}ディレクティブによるDOI自動引用にも切り替え、Kanno et al.(2023)の参照がビルド時に10.48550/arXiv.2302.11320から組み立てられます(#380)。CLAUDE.mdを、現行のトランスパイラパイプライン図(validate_entrypoint、substitute、resolve_parameter_shapes、unroll_recursion、affine_validate、classical_lowering、validate_symbolic_shapesを含む)、bindings対parametersの契約、IR Abstraction Principle、コミット / PR / Issueのワークフロールールで更新しました(#364)。