グループ演算子の用語:演算子の構造を使用する
グループ化は、演算子の項をその構造に基づいて分割するルーチンをまとめた qiskit_fermions.operators.terms、の操作の一つである。
オペレーター ガイドに記載されているように、オペレーターはグループ配列を格納することができます。これは、各項をグループインデックスに関連付ける、スパースデータ構造のオプション部分です。 同じグループインデックスを持つ用語は、1つのグループを構成します。 これにより、下流の処理においてこの構造を体系的に活用することができます。
関連する用語を(物理的性質、代数的な関係、あるいは問題特有の対称性などに基づいて)グループ化することで、次のようなメリットが得られます:
- 最適化された回路合成 :グループ化された項は、スタックを通じて問題固有の情報を伝達するため、合成された回路の最適化をより簡素化することができます。 その結果、回路の深さとゲート数が削減されます。
- 物理的構造の保持 :グループは、元の問題から得られる意味のある構造(例えば、相互作用のパターンや対称性など)を符号化し、それにより、物理的により意味のある分解が可能となる。
- フローセットの選択 : [1] で示されているように、グループ化によって、フローセットや量子シミュレーションに関連するその他のグラフ論的構造を自然に表現することができる。
- アルゴリズムの安定性の向上 : SqDRIFT ルーチンで示されているように、物理的性質が保持されることにより、一部のアルゴリズムでは安定性や性能が向上する場合があります。
- 積の公式による要因 :フェルミオン系の積の公式は、演算子をそのグループごとに分解するため、このグループ分けによって、時間発展の計算コストとトロッター誤差の両方が決まる。 「フェルミオン空間におけるエボリューション・ゲートの合成」 を参照のこと。
使用法
演算子の結合順序を定義する最も簡単な方法は、対象の演算子の属 groups 性を設定することです。 以下に、 [1] の図 1c に示されているようなラインフローセットに基づいて項をグループ化する例を示します。
このモジュール qiskit_fermions.operators.terms.grouping は、演算子内の構造を自動的に識別し、それに応じてグループインデックスを割り当てるための便利なメソッドを提供します。
>>> import rustworkx as rx
>>> graph = rx.PyDiGraph()
>>> _ = graph.add_nodes_from(range(6))
>>> edges = [(0, 1), (1, 2), (3, 0), (1, 4), (5, 2), (5, 4), (4, 3)]
>>> groups = [0, 0, 1, 2, 1, 3, 3]
>>> edges_with_group_as_payload = [(i, j, g) for (i, j), g in zip(edges, groups)]
>>> _ = graph.add_edges_from(edges_with_group_as_payload)
>>> from rustworkx.visualization import mpl_draw
>>> mpl_draw(
... graph,
... pos={i: (i % 3, i // 3) for i in range(6)},
... edge_labels=str,
... with_labels=True,
... )
<Figure size ... with 1 Axes>
これは、有向辺で結ばれたマジョラナモードからなる、2×3の単純な格子です。 各辺は、接続された2つのモードに作用する演算子の項を表しています。 目的は、これらの用語を(グラフ上に示されている)エッジのラベルに基づいてグループ分けすることです。 同じラベルを持つエッジで結びつけられた用語は、同じグループに配置されます。 以下のコードは、その方法を示しています:
>>> from qiskit_fermions.operators import MajoranaOperator
>>>
>>> # Construct operator directly using sparse arrays
>>> # 7 terms, each with coefficient 1.0, modes from edge endpoints
>>> op = MajoranaOperator(
... coeffs=[1.0] * 7,
... modes=[node for edge in edges for node in edge],
... boundaries=[0, 2, 4, 6, 8, 10, 12, 14],
... )
>>>
>>> # Assign group indices to terms (groups is part of the sparse data structure)
>>> op.groups = groups
>>>
>>> # Partition operator by groups
>>> grouped_ops = op.split_out_groups()
>>>
>>> # Inspect each group
>>> for i, g in enumerate(grouped_ops):
... print(f"Group {i}: {list(sorted(g.iter_terms()))}")
Group 0: [([0, 1], (1+0j)), ([1, 2], (1+0j))]
Group 1: [([3, 0], (1+0j)), ([5, 2], (1+0j))]
Group 2: [([1, 4], (1+0j))]
Group 3: [([4, 3], (1+0j)), ([5, 4], (1+0j))]#include <qiskit_fermions.h>
uint64_t num_terms = 7;
uint64_t num_modes = 14;
uint32_t modes[14] = {0, 1, 1, 2, 3, 0, 1, 4, 5, 2, 5, 4, 4, 3};
QkComplex64 coeffs[7] = {{1.0, 0.0}, {1.0, 0.0}, {1.0, 0.0}, {1.0, 0.0},
{1.0, 0.0}, {1.0, 0.0}, {1.0, 0.0}};
uint32_t boundaries[8] = {0, 2, 4, 6, 8, 10, 12, 14};
QfMajoranaOperator *op = qf_maj_op_new(num_terms, num_modes, coeffs, modes, boundaries);
uint32_t groups[7] = {0, 0, 1, 2, 1, 3, 3};
qf_maj_op_set_groups(op, groups, num_terms);
QfMajoranaOperator *group_ops[4];
qf_maj_op_split_out_groups(op, NULL, 0, group_ops);グループを分析する
グループはそれ自体に意味を規定するものではない。グループは、どの用語が互いに関連しているかを記録するものであり、その理由を決して示すことはない。 このライブラリには、グループ分けが「正しい」かどうかを報告するルーチンはありません。なぜなら、報告の基準となる「正しさ」という単一の概念が存在しないからです。 ただし、下流のコンシューマーはグループ化の性質を前提としているため、このセクションで紹介する関数を使えば、呼び出しのたびにその代償を払うことなく、そのような前提を事前に確認することができます。
最も一般的な慣例として、各群が個別にエルミート群であることが挙げられる。 それこそが、フェルミオン型ランダム化積公式を可能にしている理由である。すなわち、 を持つ単独の はエルミート的ではないため、その時間発展はユニタリではなく、単独ではサンプリングすることができない。 これを と組み合わせると、次のようなグループが得られます。
>>> from qiskit_fermions.operators import FermionOperator
>>> from qiskit_fermions.operators.terms.grouping import (
... group_coeff_means,
... groups_are_hermitian,
... groups_have_uniform_coeffs,
... )
>>>
>>> # a conjugate pair, plus one unpaired term. The operator is built from the sparse
>>> # arrays rather than from a dictionary, because only the former fixes the term order
>>> # that the group indices below are paired with.
>>> pair_and_single = FermionOperator(
... [1.0, 1.0, 1.0],
... [True, False, True, False, True, False],
... [0, 1, 1, 0, 2, 3],
... [0, 2, 4, 6],
... )
>>> pair_and_single.groups = [0, 0, 1]
>>>
>>> groups_are_hermitian(pair_and_single) # group 1 is unpaired
[True, False]
>>> group_coeff_means(pair_and_single)
[1.0, 1.0]#include <qiskit_fermions.h>
uint64_t num_terms = 3;
uint64_t num_actions = 6;
bool actions[6] = {true, false, true, false, true, false};
uint32_t modes[6] = {0, 1, 1, 0, 2, 3};
QkComplex64 coeffs[3] = {{1.0, 0.0}, {1.0, 0.0}, {1.0, 0.0}};
uint32_t boundaries[4] = {0, 2, 4, 6};
QfFermionOperator *op =
qf_ferm_op_new(num_terms, num_actions, coeffs, actions, modes, boundaries);
uint32_t groups[3] = {0, 0, 1};
qf_ferm_op_set_groups(op, groups, num_terms);
bool hermitian[2];
qf_ferm_op_groups_are_hermitian(op, 1e-8, hermitian);
double means[2];
qf_ferm_op_group_coeff_means(op, means);1学期につき1つのグループインデックスが必須となります
グループ配列は項ごとに1つのインデックスを保持しており、これとは異なる長さのインデックスを割り当てると、エラーが発生します。 下流の処理では不変条件が再チェックされないため、インデックスが演算子に渡される時点でその条件が強制されます。つまり、配列の長さが短すぎると末尾の項が黙って省略され、長すぎると、どの項にも含まれていない群が作り出されてしまいます。
>>> pair_and_single.groups = [0, 0] # one index short, on a three-term operator
Traceback (most recent call last):
ValueError: expected one group index per term, but got 2 for 3 termsを代入してグループインデックスをクリアすることは、常に許可 None されています。
この2つのチェックは互いに独立しています
groups_have_uniform_coeffs() ある群の係数が一致しているかどうかを報告する。これは、その仮定が絶対値の平均を算出する際に用いる group_coeff_means() ものである。 これは、ハーミティシティチェックの弱形というわけではありません。どちらのチェックも、どちらの方向においても、もう一方を必然的に意味するものではありません。
>>> from qiskit_fermions.operators.terms.grouping import (
... groups_are_hermitian,
... groups_have_uniform_coeffs,
... )
>>> # uniform coefficients, but not Hermitian: the terms are not each other's adjoint
>>> uniform = FermionOperator(
... [1.0, 1.0],
... [True, False, True, False],
... [0, 1, 2, 3],
... [0, 2, 4],
... )
>>> uniform.groups = [0, 0]
>>> groups_have_uniform_coeffs(uniform), groups_are_hermitian(uniform)
([True], [False])>>> # Hermitian, but with mixed magnitudes: two conjugate pairs at different scales
>>> mixed = FermionOperator(
... [1.0, 1.0, 5.0, 5.0],
... [True, False, True, False, True, False, True, False],
... [0, 1, 1, 0, 2, 3, 3, 2],
... [0, 2, 4, 6, 8],
... )
>>> mixed.groups = [0, 0, 0, 0]
>>> groups_are_hermitian(mixed), groups_have_uniform_coeffs(mixed)
([True], [False])デフォルトでは、絶対値が比較されます。なぜなら、それが「 group_coeff_means() 平均」となるからです。 複素係数を持つエルミート共役対は、絶対値は等しいが係数は等しくないため、デフォルト形式のみを満たす。
>>> conjugate = FermionOperator(
... [1.0j, -1.0j],
... [True, False, True, False],
... [0, 1, 1, 0],
... [0, 2, 4],
... )
>>> conjugate.groups = [0, 0]
>>> groups_have_uniform_coeffs(conjugate)
[True]
>>> groups_have_uniform_coeffs(conjugate, abs=False)
[False]groups_are_hermitian() は、以下の片側保証を継承する is_hermitian():a の True エントリは常に信頼できるが、a の False エントリは、その正規形が真の標準形である演算子型についてのみ信頼できる。 どのタイプが該当するかについては、具体的なオペレーターの種類をご確認ください。
A。 Gandon ら、 局所的な量子ビット符号化を用いたフェルミオンダイナミクスのスタビライザーベース量子シミュレーション、 arXiv:2512.11418v2。