Skip to main content
IBM Quantum Platform

演算子表現の設計原則

このガイドでは、この operators モジュール内のすべてのオペレータ表現に共通する、一般的な設計原則と中核となる概念について説明します。


概要

このモジュールが提供するオペレータ表現には、いくつかの基本的な設計原則が共通しています:

  • スパースデータ構造 :演算子は、自己同一でない演算のみをエンコードする。 内部のデータレイアウトは、一般的に疎行列のデータ形式を参考にしており、モード数は多いものの、有意な寄与が比較的少ないシステムにおいて、効率的な保存と計算を可能にしています。
  • 項の反復処理と再構築 :内部のスパースな格納形式にかかわらず、演算子は一貫性のある反復処理インターフェースを提供するため、基盤となるデータ構造を理解していなくても項を検査、フィルタリング、変換することができ、変更された項から新しい演算子を再構築することができます。
  • モードに基づくインデックス付け :演算子は、フェルミオンの自由度をラベル付けするために抽象的なモードインデックスを用い、物理系から演算子表現への柔軟な対応付けを可能にする。
  • 項のグループ化と交換法則 :演算子は、項とグループ添字を関連付けるグループ化情報をネイティブにサポートしています。 これにより、個別のデータ構造を用意することなく、最適化や物理構造の保持が可能になります。 具体的な使い方については、 グループ化ガイドをご覧ください。
  • 算術演算および数学的演算 :すべてのオペレータは、 OperatorTrait プロトコルを使用して一貫性のある算術演算(加算、乗算、合成など)および数学的関数のセットを実装しており、これにより、異なるオペレータタイプ間でもコードを統一することが可能になります。
  • 演算子の順序と標準形 :数学的に同等の演算子であっても、量子アルゴリズムにおいては、その表現や挙動が大きく異なる場合がある。 すべての演算子表現は、(代数固有の交換関係に基づく)さまざまな標準形をサポートしており、これにより、規範的で予測可能かつ最適化可能な演算子表現を実現しています。

疎なデータ構造

すべての演算子は、非恒等演算のみを追跡する疎表現を使用しています。 各非同一性演算は、 係数 (複素数)と、特定のモードに対する一連の操作から構成される。

このアプローチにより、特にモード数は多いものの、実質的な寄与が比較的少ないシステムにおいて、メモリ使用量と計算時間を劇的に削減することができます。 非同一性演算のみを符号化することで、演算は本質的な部分だけに焦点を当てることができ、これにより、密表現では実現が困難な大規模システムでの処理が可能になる。 さらに、演算子は当然ながら、任意の数のモードに対して拡張可能です。モードに対して作用する演算子は、影響を受けないモードが暗黙的に恒等演算子となるため、はるかに多くのモードを持つシステムにおいても、変更されることなく機能 {0, 1} します。

算術演算の際、同一の項は個別に保持されるため、必要に応じて明示的に結合する必要があります。

内部ストレージのフォーマット

内部的には、演算子は疎行列のデータ形式を参考にした配列に格納されています:

  • 係数配列 :各項の複素係数
  • モードインデックス配列 :各作用が作用するフェルミオンモード
  • 境界配列 :モード配列内で各項のモードがどこから始まり、どこで終わるかを示すインデックス
重要

演算子の種類によっては、追加の配列が存在する場合があります。 例えば、インスタンス FermionOperator には、それぞれのモードインデックスに作用するフェルミオン作用の種類を指定する、ブール値の配列である「actions」が含まれます。 対照的に、このクラス MajoranaOperator では、モードインデックスのパリティにその情報がエンコードされているため、この区別は必要ありません。 ストレージ形式の詳細については、ご利用のオペレータータイプに対応するAPIドキュメントをご参照ください。

以下の例は、これらの配列がどのように構成されているかを示しています。 まずは、スパース配列を用いた直接的な構成方法です:

[x] PYTHON

>>> from qiskit_fermions.operators import FermionOperator
>>>
>>> # Construct operators directly using sparse arrays
>>> # First operator: 1.0 * +0 -1
>>> op1 = FermionOperator(
...     coeffs=[1.0],
...     actions=[True, False],
...     modes=[0, 1],
...     boundaries=[0, 2],
... )
>>>
>>> # Second operator: 1.0 * +2 -3
>>> op2 = FermionOperator(
...     coeffs=[1.0],
...     actions=[True, False],
...     modes=[2, 3],
...     boundaries=[0, 2],
... )
>>>
>>> # Combine the sparse operators
>>> op1 += op2
>>> print(format(op1))
  1.000000e0 +0.000000e0j * (+0 -1)
  1.000000e0 +0.000000e0j * (+2 -3)

[] C

#include <qiskit_fermions.h>

// Construct first operator: 1.0 * c_0 a_1
QkComplex67 coeff1[1] = {{1.0, 0.0}};
uint32_t modes1[2] = {0, 1};
uint32_t boundaries1[2] = {0, 2};
QfFermionOperator *op1 = qf_ferm_op_new(1, 2, coeff1, modes1, boundaries1);

// Construct second operator: 1.0 * c_2 a_3
QkComplex67 coeff2[1] = {{1.0, 0.0}};
uint32_t modes2[2] = {2, 3};
uint32_t boundaries2[2] = {0, 2};
QfFermionOperator *op2 = qf_ferm_op_new(1, 2, coeff2, modes2, boundaries2);

// Add operators
qf_ferm_op_add_assign(op1, op2);

qf_ferm_op_free(op1);
qf_ferm_op_free(op2);

手軽な工法

Python の開発者には、疎配列の保存に関する詳細を抽象化した、便利な構築メソッドがいくつか用意されています。 これらを使えば、係数、モード、境界配列の管理を気にすることなく、演算子を簡単に構築できます:

[x] PYTHON

>>> from qiskit_fermions.operators import FermionOperator, cre, ann
>>>
>>> # Construct operators using operator algebra notation
>>> op1 = FermionOperator.from_dict({(cre(0), ann(1)): 1.0})
>>> op2 = FermionOperator.from_dict({(cre(2), ann(3)): 1.0})
>>>
>>> # The result is sparse even when combining them
>>> op1 += op2
>>> print(format(op1))
1.000000e0 +0.000000e0j * (+0 -1)
1.000000e0 +0.000000e0j * (+2 -3)

[] C

// The C API uses direct array construction; convenience methods are not available.
ヒント

個々のオペレータの実装によっては、その特定のユースケースに適した追加のコンストラクタがサポートされている場合があります。 ご利用のオペレータタイプに対応するAPIドキュメントを確認し、利用可能なすべての構築オプションをご確認ください。


項の反復と再構成

演算子は、内部の疎な表現 OperatorTrait.iter_terms() にかかわらず、一貫した反復インターフェースを提供します。 したがって、基礎となるデータ構造を理解していなくても、項を検査、フィルタリング、または変換することができます。 その後、…を用いて、変換された項から新しい演算子を再構成することができます OperatorTrait.from_terms()

[x] PYTHON

>>> from qiskit_fermions.operators import FermionOperator, cre, ann
>>>
>>> # Construct an operator with terms of different orders
>>> op = FermionOperator.from_dict({
...     (): 0.5,  # constant term (order 0)
...     (cre(0), ann(1)): 1.0,  # two-body term (order 2)
...     (cre(0), cre(1), ann(1), ann(0)): 0.25  # four-body term (order 4)
... })
>>>
>>> # Filter to keep only terms of order 2
>>> order_two_terms = [
...     (term, coeff) for term, coeff in op.iter_terms()
...     if len(term) == 2
... ]
>>>
>>> # Reconstruct operator from filtered terms
>>> filtered_op = FermionOperator.from_terms(order_two_terms)
>>> print(f"Original operator has {len(op)} terms")
Original operator has 3 terms
>>> print(f"Filtered operator has {len(filtered_op)} term")
Filtered operator has 1 term

[] C

// WARNING: Term iteration and filtering are not yet available in the C API.

モードベースのインデックス作成

すべての演算子の表現は、その項が作用する添字を「 モード」 として扱います。 モードとは、単に、系におけるフェルミオンの自由度を特定するための指標のことです。 物理的な自由度(空間軌道、スピン状態、その他の量子数など)からモード指数への対応付けはユーザーに委ねられており、最大限の柔軟性が確保されています。

この抽象化は、 qiskit_fermions.circuit モジュール内にも見られ、そこでは がフェルミオンモードのレジスタに対して作用 FermionicCircuit する。 演算子表現と回路表現のいずれにおいても、モードは、特定の演算にどの自由度が関与するかを指定するための、一貫性があり、代数に依存しない方法を提供する。

重要

現在の実装ではスピンレスモードが使用されています。このモジュールが現在提供しているすべての演算子表現では、モードをスピンレスのフェルミオン的自由度として扱っています。 つまり、システムにスピンアップとスピンダウンの両方の電子またはフェルミオンが存在する場合、それらを明確に異なるモードに割り当てる必要があります(例えば、4つの空間軌道において、スピンアップをモード0~3、スピンダウンをモード4~7とする、あるいは任意の他の規則を採用するなど)。

この設計では、特定のスピン順序の規則を強制することを避けつつ、コア表現をシンプルかつ汎用的なものに保っています。 のようなユーティリティモジュールは、電子構造データを読み込む際に、こうしたマッピングを自動的に処理してくれる便利な関数(例えば、 FCIDump.from_file())を提供 qiskit_fermions.operators.library しています。

ヒント

パッケージの進化に伴い、データモデル内でスピン自由度をネイティブにサポートするスピンフルな演算子表現が追加される可能性があります。 これらは、現在のスピンレス実装とは明確に区別され、モジュール内でそれらと共存することになります。


項の結集と交換関係

係数やモードインデックスと同様に、演算子は、各項をグループインデックスに関連付ける「 groups」配列を、必要に応じて格納することができます。 疎なデータ構造の一部として、グループ化を演算子の表現に直接組み込むことで、グループ化情報は変換の過程において演算子とともに自然に伝播する。 これにより、物理的性質、代数的な関係、あるいは問題特有の対称性など、あらゆる観点から構造を体系的に活用することが可能になります。 構造化された情報は、その後、...といった手法を用いて、回路合成や分解といった下流の処理に活用することができます OperatorTrait.split_out_groups()

ワークフロー内で演算子項をグループ化する方法の詳細については、 グループ化ガイドを参照してください。

[x] PYTHON

>>> from qiskit_fermions.operators import MajoranaOperator, gamma
>>> op = MajoranaOperator.from_dict({
...     (gamma(0, False),): 1.0,
...     (gamma(1, False),): 1.0,
...     (gamma(2, False), gamma(3, False)): 1.0
... })
>>> # Assign group indices to terms
>>> op.groups = [0, 0, 1]
>>> # Partition operator by groups
>>> grouped_ops = op.split_out_groups()

[] C

#include <qiskit_fermions.h>

// Create operator with 3 terms
QkComplex67 coeffs[3] = {{1.0, 0.0}, {1.0, 0.0}, {1.0, 0.0}};
uint32_t modes[4] = {0, 1, 2, 3};
uint32_t boundaries[4] = {0, 1, 2, 4};
QfMajoranaOperator *op = qf_maj_op_new(3, 4, coeffs, modes, boundaries);

// Assign grouping information
uint32_t groups[3] = {0, 0, 1};
qf_maj_op_set_groups(op, groups, 3);

// Partition operator by groups
QfMajoranaOperator *grouped_ops[2];
qf_maj_op_split_out_groups(op, NULL, 0, grouped_ops);

算術および数学的演算

すべてのオペレーターは、この OperatorTrait プロトコルを実装しており、これにより、異なる種類のオペレーター間で統一された一連の操作が提供されます。 これにより、ある演算子の表現形式向けに記述されたコードが、他の演算子の表現形式でも一貫して動作することが保証されます。 このプロトコルが、このパッケージで定義されている他のプロトコルとどのように関連しているか qiskit_fermions.protocols については、を参照してください。

このプロトコルには、算術演算(加算、乗算、合成など)、構造演算(項の反復、モードサポート解析、再ラベル付け)、数学的関数(正規順序付け、簡略化、同値性チェック)などが含まれています。 利用可能なすべての操作に関する完全なリファレンスについては、ドキュメント OperatorTrait を参照してください。

[x] PYTHON

>>> from qiskit_fermions.operators import FermionOperator, cre, ann
>>>
>>> # Construct a Hermitian operator: H = +0 -1 + +1 -0
>>> op = FermionOperator.from_dict({
...     (cre(0), ann(1)): 1.0,
...     (cre(1), ann(0)): 1.0
... })
>>>
>>> # Check if the operator is Hermitian by verifying H - H† = 0
>>> adjoint = op.adjoint()
>>> difference = op - adjoint
>>> difference = difference.normal_ordered()
>>> difference = difference.simplify(atol=1e-10)
>>> is_hermitian = difference.equiv(FermionOperator.zero(), atol=1e-10)
>>> print(f"Operator is Hermitian: {is_hermitian}")
Operator is Hermitian: True

[] C

#include <qiskit_fermions.h>

// Construct a Hermitian operator: H = +0 -1 + +1 -0
QkComplex67 coeffs[2] = {{1.0, 0.0}, {1.0, 0.0}};
uint32_t modes[4] = {0, 1, 1, 0};
uint32_t boundaries[3] = {0, 2, 4};
QfFermionOperator *op = qf_ferm_op_new(2, 4, coeffs, modes, boundaries);

// Check if Hermitian: compute H - H†, normal-order, and simplify
QfFermionOperator *adjoint = qf_ferm_op_adjoint(op);
QfFermionOperator *difference = qf_ferm_op_sub(op, adjoint);
QfFermionOperator *normal_ordered = qf_ferm_op_normal_ordered(difference);
qf_ferm_op_ichop(normal_ordered, 1e-10);

QfFermionOperator *zero = qf_ferm_op_zero();
bool is_hermitian = qf_ferm_op_equiv(normal_ordered, zero, 1e-10);
printf("Operator is Hermitian: %s\n", is_hermitian ? "true" : "false");

// Clean up
qf_ferm_op_free(op);
qf_ferm_op_free(adjoint);
qf_ferm_op_free(difference);
qf_ferm_op_free(normal_ordered);
qf_ferm_op_free(zero);
重要

この例では、と simplify()atol=1e-10 両方で が使用されています equiv()。 ( atol 絶対許容差) パラメータは、しきい値を指定します。 絶対値が より小さい係数はゼロとして扱われ atol 、除外される。 演算子を比較する際の数値的安定性を確保するには、これが不可欠です。なぜなら、浮動小数点演算ではわずかな丸め誤差が生じることがあり、それがなければ、同等の演算子であってもそのように認識されなくなる可能性があるからです。

ヒント

このプロトコ OperatorTrait ルは共通のインターフェースを提供していますが、個々のオペレータの実装によっては、プロトコルには含まれていない追加の利便性向上のためのメソッドが提供されている場合があります。 たとえば、一部のオペレーターでは、このチェックを実装するメソッド is_hermitian() が提供されています。 利用可能なすべての機能を確認するには、必ずお使いのオペレータータイプのAPIドキュメントを参照してください。


演算子の順序と標準形

量子演算子代数における根本的な課題は、数学的に同等の演算子が多くの異なる方法で表現可能であり、それぞれが量子アルゴリズムに対して異なる意味合いを持つという点にある。 同じ演算子は、代数的に同値な形で記述することができます(例えば、 aba^\dagger bba+[a,b]ba^\dagger + [a^\dagger,b] と表すことができます)。しかし、これらの表現は、回路合成、簡略化、および数値アルゴリズムにおいて異なる挙動をもたらします。

演算子の表現は、可換関係を用いて演算子を代数固有の標準形に変換する、 通常の順序付け演算をサポートしています。 これにより、信頼性の高い比較が可能となり(2つの同等の演算子は同一の正規順序形を持つ)、簡略化が明らかになり(可換関係により項が相殺されたり結合されたりする)、また、正しさや効率性のために特定の演算子形式を必要とするアルゴリズムがサポートされる。

重要

演算子の表現方法によって、それぞれの代数に適した異なる交換関係に基づいて、正規順序が実装される場合があります。 たとえば、フェルミオン型ノーマルオーダーでは反交換関係( {ci,cj}=δij\{c_i, c_j^\dagger\} = \delta_{ij} )が用いられるのに対し、マジョラナ型ノーマルオーダーでは異なる代数規則( {γi,γj}=2δij\{\gamma_i, \gamma_j\} = 2\delta_{ij} )が用いられます。ノーマルオーダーがどのように実装されているかを理解するには、常に使用する演算子のドキュメントを参照してください。

[x] PYTHON

>>> from qiskit_fermions.operators import FermionOperator, cre, ann
>>>
>>> # Two different representations of the same operator
>>> op1 = FermionOperator.from_dict({(ann(0), cre(0)): 1.0})
>>> op2 = FermionOperator.from_dict({(): 1.0, (cre(0), ann(0)): -1.0})
>>>
>>> # Direct comparison fails due to different forms
>>> op1.equiv(op2, atol=1e-10)
False
>>>
>>> # Normal-order both and compare again
>>> op1_normal = op1.normal_ordered()
>>> op2_normal = op2.normal_ordered()
>>> op1_normal.equiv(op2_normal, atol=1e-10)
True

[] C

#include <qiskit_fermions.h>
#include <stdbool.h>

// Two different representations of the same operator
QkComplex67 coeff1[1] = {{1.0, 0.0}};
uint32_t modes1[2] = {0, 0};
uint32_t boundaries1[3] = {0, 2};
QfFermionOperator *op1 = qf_ferm_op_new(1, 2, coeff1, modes1, boundaries1);

QkComplex67 coeff2[2] = {{1.0, 0.0}, {-1.0, 0.0}};
uint32_t modes2[2] = {0, 0};
uint32_t boundaries2[3] = {0, 0, 2};
QfFermionOperator *op2 = qf_ferm_op_new(2, 2, coeff2, modes2, boundaries2);

// Direct comparison fails due to different forms
bool equiv_before = qf_ferm_op_equiv(op1, op2, 1e-10);
printf("Equivalent before normal ordering: %s\n", equiv_before ? "true" : "false");

// Normal-order both and compare again
QfFermionOperator *op1_normal = qf_ferm_op_normal_ordered(op1);
QfFermionOperator *op2_normal = qf_ferm_op_normal_ordered(op2);
bool equiv_after = qf_ferm_op_equiv(op1_normal, op2_normal, 1e-10);
printf("Equivalent after normal ordering: %s\n", equiv_after ? "true" : "false");

// Clean up
qf_ferm_op_free(op1);
qf_ferm_op_free(op2);
qf_ferm_op_free(op1_normal);
qf_ferm_op_free(op2_normal);
ヒント

このプロトコル OperatorTrait.normal_ordered() 方式では、位置引数およびキーワード引数を意図的に未定義としており、具体的な演算子の実装において、生成される正確な標準形を制御する調整可能なパラメータを定義できるようにしています。 これにより、演算子ごとに最適化を行ったり、その代数やユースケースに合わせて正規形のバリエーションを調整したりすることが可能になります。 ご利用のオペレータータイプに対応するAPIドキュメントを確認し、利用可能なパラメータを確認してください。

このページは役に立ちましたか?
バグや誤字の報告、またはコンテンツの要求はGitHubで行ってください。