演算子の項のグループ化:演算子の構造を使う
BosonOperatorは、各項にグループ・インデックスのタグを付けることができます。タグ自体には意味がなく、どの項が一緒に属するかを示すだけです。タグが何を意味するかは、それを利用するものが決めます。このパッケージでは、利用するのは、グループごとに1つの因子に分割される時間発展ゲートと、グループ全体をサンプリングするランダム化されたqDRIFTパスです。
このガイドでは、グループの割り当て方、利用側が依存する性質の確認方法、そして大きなグループから少数のグループを取り出す操作を低コストに保つための演算子の配置方法を示します。
このパッケージには自動グループ化の戦略は同梱されていません。モデルについて知っていることをもとに、インデックスを自分で割り当てます。
グループを割り当てる
ここで使う例は、3サイトのボース・ハバード・チェーンです。各ボンドはホッピング対を提供し、サイト内相互作用は対角な部分を構成します。グループ化された演算子を構築する最も簡単な方法はfrom_terms_with_groups()で、各項をそのグループ・インデックスとともに(actions, coefficient, group)の3つ組として受け取ります。項を書き下す時点でグループを決めるため、その項がなぜそこに属するかをまだ把握しています。
>>> from qiskit_bosons.operators import BosonOperator, ann, cre
>>>
>>> bonds = [(0, 1), (1, 2)]
>>> terms = []
>>> for group, (left, right) in enumerate(bonds): # one group per bond
... terms.append(([cre(left), ann(right)], -1.0, group))
... terms.append(([cre(right), ann(left)], -1.0, group))
>>> for site in range(3): # the on-site interactions share one group
... terms.append(([cre(site), cre(site), ann(site), ann(site)], 1.0, len(bonds)))
>>> hamiltonian = BosonOperator.from_terms_with_groups(terms)
>>> len(hamiltonian), hamiltonian.num_groups()
(7, 3)
項は与えた順序で保存されるため、グループ・インデックスが誤った項に付いてしまうことはありません。
すでに存在する演算子は、groups属性を代入することで、後からグループ化する必要があります。from_dict()は項を保存する順序を規定していないため、ここで注意が必要です。位置でリストを書き出すと、インデックスが誤った項に付くことがあります。代わりに、各インデックスを項自体から導出してください。
>>> existing = BosonOperator.from_dict({tuple(actions): coeff for actions, coeff, _ in terms})
>>> existing.groups = [
... len(bonds)
... if len({mode for _, mode in term}) == 1
... else bonds.index(tuple(sorted({mode for _, mode in term})))
... for term, _ in existing.iter_terms()
... ]
>>> existing.num_groups(), existing.equiv(hamiltonian)
(3, True)
インデックスの数は項の数と一致しなければなりません。それ以外は拒否されます。短すぎるリストでは、項をグループとともに反復処理する場所で末尾の項が暗黙のうちに落ちてしまうからです。
>>> try:
... hamiltonian.groups = [0, 1]
... except ValueError as error:
... print(error)
expected one group index per term, but got 2 for 7 terms
利用側が依存するものを確認する
qiskit_bosons.operators.termsの3つの関数は、既存のグループ化について、それぞれ1つの限定された問いに答えます。どれも、グループ化が「正しい」とは言いません。正しさは利用側に対して相対的なものだからです。
各グループはエルミートですか。 これが最も重要な性質です。グループの時間発展は、そのグループがエルミートである場合にのみユニタリーであり、単独のホッピング項 はエルミートではありません。随伴との対だけがエルミートです。
>>> from qiskit_bosons.operators.terms import groups_are_hermitian
>>> groups_are_hermitian(hamiltonian)
[True, True, True]
ホッピング対を分割するグループ化は、すぐに検出されます。
>>> split = BosonOperator.from_terms_with_groups(
... [(actions, coeff, index) for index, (actions, coeff, _) in enumerate(terms)]
... ) # one term per group
>>> [bool(flag) for flag in groups_are_hermitian(split)]
[False, False, False, False, True, True, True]
4つのホッピング項は検出されています。3つの True のエントリーはオンサイト項 で、これらはそれ自体でエルミートです。モードが1つの項の中で繰り返されることは正当であり、そのために何かが除外されることはありません。フラグは terms の順序に従うので、各フラグを、それが表す項と照らし合わせて読むことができます。
係数は一様ですか? ランダム化積公式は、グループの係数の平均大きさでそのグループを重み付けします。これは、大きさがそろっている場合にのみ、そのグループの忠実な記述になります。
>>> from qiskit_bosons.operators.terms import group_coeff_means, groups_have_uniform_coeffs
>>> groups_have_uniform_coeffs(hamiltonian)
[True, True, True]
>>> group_coeff_means(hamiltonian)
[1.0, 1.0, 1.0]
どちらのチェックも他方を含意しません。係数が一様でもグループがエルミートになるとは限らず、エルミートなグループでも大きさが混在することがあります。デフォルトでは大きさが比較されます。これは group_coeff_means() が平均する対象だからです。複素係数を持つエルミートなペアは、大きさは等しくても係数は等しくないため、デフォルトの形式のみを満たします。
>>> conjugate = BosonOperator.from_terms_with_groups(
... [([cre(0), ann(1)], 1.0j, 0), ([cre(1), ann(0)], -1.0j, 0)]
... )
>>> groups_have_uniform_coeffs(conjugate)
[True]
>>> groups_have_uniform_coeffs(conjugate, abs=False)
[False]
groups_are_hermitian() は is_hermitian() をもとに構築されており、正規順序化の後で演算子とその随伴を比較します。これは切り捨てなしの代数に関する恒等式であるため、チェックを通過したグループはボソン演算子としてエルミートです。生成・消滅演算子の積がどのように順序付けされ比較されるかについては、演算子表現の設計原則を参照してください。
項をグループごとに並べる
split_out_groups() は、要求された各グループにつき1つの演算子を返します。グループの項が演算子の中に散らばっている場合、それらを見つけるためにすべての項を走査する必要があります。各グループが1つの連続した区間を形成している場合は、代わりにグループの境界を二分探索するため、検索のコストは保持されている項の数ではなく、要求されたグループの数に従います。
group_order() はそのレイアウトを生成します。これはグループインデックスによる安定ソートです。演算子の値は変わらず、グループ内の項は相対的な順序を保ちます。
>>> from qiskit_bosons.operators.terms import group_order
>>> scattered = BosonOperator.from_terms_with_groups(
... [(actions, coeff, group) for (actions, coeff, _), group in zip(terms, [1, 0, 1, 0, 2, 2, 2])]
... )
>>> ordered = group_order(scattered)
>>> ordered.groups
[0, 0, 1, 1, 2, 2, 2]
>>> ordered.equiv(scattered)
True
どちらのレイアウトも同じ演算子を返します。違うのはコストだけです。
>>> request = [2, 0]
>>> all(
... a.equiv(b)
... for a, b in zip(
... scattered.split_out_groups(request), ordered.split_out_groups(request), strict=True
... )
... )
True
次のステップ
グループ化は、モデルの構造がまだ見えているこの段階、つまりボソンのレベルで選択されます。
-
ボソン空間でEvolutionゲートを合成するでは、これを使って時間発展をトロッター分解する方法と、それによって量子ビットレベルの積公式では減らせない誤差が減る理由を示します。
-
演算子表現の設計原則では、グループインデックスを保持する演算子データ構造と、その項がどのように格納され順序付けされるかを説明します。
-
2. マッピングの前にグループ化するでは、完全なモデルにグループ化を適用します。