Qamomile v0.12.1はv0.12.0に続く改善リリースで、フロントエンドの操作性とバグ修正に注力しています。フロントエンドには2つの使い勝手の改善が入りました — 単一qubitゲートがVector[Qubit]に対するブロードキャストに対応したこと(明示的なループを書かずにレジスタ全体への層を1行で書けるようになりました)、およびサブ@qkernelの呼び出し側がPythonのリテラル(int / float / bool)をUInt / Float / Bitパラメータに対してそのまま渡せるようになったことです。QURI Partsでgamma * Jijのようなパラメータの線形結合が扱えるようになり、QAOAの典型パターンで長らく発生していたtranspileエラーが解消されました。コンパイラ関連のバグ2件(QAOAをブロックしていたネストループの名前衝突、および上記のQURI Parts側の問題)が修正され、qaoa_graph_partitionチュートリアルはOMMXのSampleSet APIを使う構成へ書き換えられました。
pip install qamomile==0.12.1新機能¶
単一qubitゲートをqubit配列にブロードキャスト¶
単一qubitゲート(h、x、y、z、s、t、sdg、tdg、rx、ry、rz、p)がVector[Qubit]を直接受け取り、レジスタの各要素にゲートを適用するようになりました。ブロードキャスト形式は、手書きのfor i in qmc.range(n): q[i] = qmc.h(q[i])(他の単一qubitゲートも同様)が出力するのと同じForOperation IRに落ちるため、リソース推定・トランスパイル・可視化のいずれも見えるIRは同じです — ソースが短くなるだけです。回転ゲートの場合、同じスカラー角度がすべてのqubitに共有されます。qubitごとに角度を変える層は引き続きVector[Float]へ明示的にindexアクセスするループが必要です(#360)。
import qamomile.circuit as qmc
from qamomile.qiskit import QiskitTranspiler
@qmc.qkernel
def rotation_layer_broadcast(n: qmc.UInt, theta: qmc.Float) -> qmc.Vector[qmc.Bit]:
q = qmc.qubit_array(n, name="q")
q = qmc.h(q) # レジスタ全体にHadamardをブロードキャスト
q = qmc.ry(q, theta) # 共有スカラー角度をレジスタ全体にブロードキャスト
return qmc.measure(q)
transpiler = QiskitTranspiler()
exe = transpiler.transpile(
rotation_layer_broadcast,
bindings={"n": 4},
parameters=["theta"],
)ブロードキャストと明示的なループを並べて比較する解説は、更新されたチュートリアル02 — パラメータ付き量子カーネルを参照してください。
サブqkernel呼び出しでのスカラーリテラル自動昇格¶
サブ@qkernelがスカラー型qmc.UInt / qmc.Float / qmc.Bitのパラメータを宣言している場合、呼び出し側はPythonリテラルをそのまま渡せるようになりました — intはUIntへ、int / floatはFloatへ、boolはBitへ昇格されます。helper(q, 0, 0.5)はhelper(q, qmc.uint(0), qmc.float_(0.5))と等価です。これはfloat | Floatを受け付けてきたqmc.rxなど、既存の組み込みゲートプリミティブのリテラル受け入れパターンと揃えたものです。副次的な効果として、def f(n: qmc.UInt = 4)のようなスカラーデフォルト値もHandleコンストラクタで明示的にラップしなくても動くようになりました(#372)。
昇格は保守的に行われます。トリガーされるのは、宣言された型が3つのスカラーHandleクラスのいずれかちょうどであり、リテラルが対応するプリミティブである場合に限られます。boolはint → UIntおよびint → Floatの経路から意図的に除外しているため、Trueが暗黙のうちにUInt(1)になることはありません。すでにHandleである引数や配列型のパラメータは変更されずに通過するため、既存の記号的実行のパスには影響しません。
import qamomile.circuit as qmc
from qamomile.qiskit import QiskitTranspiler
@qmc.qkernel
def rotate_first(
q: qmc.Vector[qmc.Qubit],
idx: qmc.UInt,
angle: qmc.Float,
) -> qmc.Vector[qmc.Qubit]:
q[idx] = qmc.ry(q[idx], angle)
return q
@qmc.qkernel
def helper_with_literals(n: qmc.UInt) -> qmc.Vector[qmc.Bit]:
q = qmc.qubit_array(n, name="q")
q = rotate_first(q, 0, 0.5) # int / floatリテラルがそのまま受け付けられる
return qmc.measure(q)
transpiler = QiskitTranspiler()
exe = transpiler.transpile(helper_with_literals, bindings={"n": 3})リテラル昇格の便利さを明示的に取り扱うように更新されたチュートリアル06 — 再利用パターンも参照してください。
バグ修正¶
QURI Partsへのトランスパイルでランタイムパラメータの線形結合が扱えるようになりました(#371)。QAOAの典型パターン
qmc.rzz(q[i], q[j], gamma * Jij)は、これまでQURI Partsではemit時にTypeErrorを出していました(同じカーネルがQiskitとCUDA-Qでは正常にトランスパイルできる)。スカラー倍やパラメータ同士の和といった線形形式がそのままQURI Partsの回路に伝わるようになり、このパターンが動作します。非線形ケース(param * param、param ** n、パラメータでの除算)はトランスパイル時にQamomileQuriPartsTranspileErrorで弾かれます。同じ表示名のループがネストしても衝突しなくなりました(#365)。2つの
@qkernelが同じ表示名のループ変数を使っている場合(例: 双方ともfor i in qmc.range(...))、emit時のindex resolverがbindings[uuid]より先にbindings[name]を引いていました。内側のカーネルをinlineすると自身の反復に対するbindings["i"]が積まれるため、外側スコープから取り込まれた式(呼び出し側のarr[i]など)が内側ループのindexに対して解決されてしまい、余分なランタイムパラメータとassign_parametersの不正な失敗を引き起こしていました。ループ変数のbindingsはUUIDオンリーになりました — 各ForOperationは新しいloop_var_valueUUIDを得るため、カーネルをまたいで同じ表示名を使っても干渉しなくなります。
ドキュメント¶
graph-partition QAOAチュートリアルが、v0.12.0で導入された
ommx.v1.SampleSetAPIを使う構成に書き換えられました。実行可能性比、最良の実行可能解、目的関数のヒストグラムは、自前のis_feasible/count_cut_edgesヘルパーではなくsummary["feasible"]、best_feasible、summary.loc[..., "objective"]から直接読み取るようになっています。COBYLAのコスト関数はQUBOドメインのペナルティ込みエネルギーが必要なため、引き続きdecode_to_binary_sampleset()を使います(#373)。旧
jij-inc.github.io/QamomileのGitHub PagesのURLが、Read the Docsへ言語別にリダイレクトするようになりました(/en/、/ja/)。古いブックマークが現行ドキュメントの対応する言語版に着地します(#367、#368)。