QEC プロトコルの開発と検証

Microsoft Quantum Development Kit (qdk.ec) の qodec モジュールと QDK を使用して、量子エラー修正 (QEC) コードを定義し、実行可能なプロトコルに変換します。 この記事では、エラー コードの定義、ベースライン qodecの構築、宣言の検証、ガジェット距離の計算、プロトコルの保存を行います。

この記事の例では、$[4,2,2]$ C4 コードを使用します。これは、2 つの論理量子ビットを 4 つの物理量子ビットに格納し、単一の物理 Pauli エラーを検出します。

[前提条件]

開始する前に、次のタスクを完了してください。

  • エラー修正のために QDK をインストールします。
  • Python 3.11 以降の環境を使用してください。
  • qodec Stim パーサーが使用可能であることを確認します。 Linux Arm64 または WindowsArm64 で、ソースから Stim をインストールし、build_qodec を呼び出す前に Python または Jupyter カーネルを再起動します。

コードを定義する

コードスタビライザーを使用し、各論理量子ビットに対して、論理 X 代表と論理 Z 代表をそれぞれ 1 つ含む qodec.Code オブジェクトを作成します。

import qodec

c4 = qodec.Code(
    "C4",
    stabilizers=["X_0 X_1 X_2 X_3", "Z_0 Z_1 Z_2 Z_3"],
    x=["X_0 X_1", "X_0 X_2"],
    z=["Z_0 Z_2", "Z_0 Z_1"],
)

qodec パッケージは、Pauli文字列にスパース表記を使用します。 各項には、Pauli を表す文字と物理量子ビット ラベルが含まれます。 たとえば、 X_0 X_2 は、物理量子ビット 0 と 2 に X を適用します。

コード定義は静的な数学オブジェクトです。 エンコードされた状態を準備、保存、または測定する回線は含まれません。

コードを分析する

コードオペレーターのスナップショットを確認するには、CodeProfileを作成してください。

from qdk import ec

profile = ec.CodeProfile(c4)

print("Physical support:", sorted(profile.support))
print("Physical qubits:", profile.length)
print("Logical qubits:", profile.logical_qubit_count)
print("Stabilizers:", profile.stabilizers)
print("Logical X operators:", profile.x)
print("Logical Z operators:", profile.z)

後で元の qodec.Code オブジェクトを変更しても、プロファイルは変更されません。

コード距離を計算する

ゼロ症候群で非トリビアル論理効果を引き起こす許容される物理的エラーの最小数を見つけるには、distanceを呼び出す。

distance = profile.distance()
print("Code distance:", distance)

error = distance.witness.product
syndrome = profile.syndrome_of(error)
logical_effect = profile.logical_effect_of(error)

print("Witness:", error)
print("Syndrome:", sorted(syndrome))
print("Logical effect:", logical_effect)

assert distance.value == 2
assert profile.is_logical(error)

既定では、この検索では、各単一量子ビット X、Y、または Z エラーにコスト 1 が割り当てられます。 返される Distance には、認定された境界と、任意の有限の上限に対する証明が含まれます。

完全で厳密な検索にかかるコストが高すぎる場合は、distance_bounds を使用します。

bounds = profile.distance_bounds(upper_bound=4)
print("Distance bounds:", bounds)
print("Lower bound:", bounds.lower_bound)
print("Upper bound:", bounds.upper_bound)

既定のソルバーは HiGHSです。 別のソルバーを使用するには、solver="enumeration"を設定するか、MWPFをインストールしてsolver="mwpf"を設定してください。

ベースラインを構築する qodec

qodecは、エンコードされた命令を実行する方法について説明します。 命令セット、コード、レイヤー、ガジェット、回路、チェック、読み出し式があります。

build_qodecを使用して、C4 コードから 2 層プロトコルを作成します。

protocol = ec.build_qodec(
    c4,
    strategy="bare-css/v1",
    strict=False,
)

この例では、 bare-css/v1 を使用して、フラグ量子ビットなしで単純なプロトコルを検査できるようにします。

Caution

bare-css/v1戦略では、フォールト トレラントな回路は保証されません。 ノイズの多い実験にプロトコルを使用する前に、各ガジェットの距離を計算します。

ビルダーは、次の論理命令を作成しようとします。

  • prepare_z_all
  • prepare_x_all
  • syndrome
  • measure_z_all
  • measure_x_all
  • h_all
  • cx_all

_allサフィックスは、命令がブロック内のすべての論理量子ビットに対して機能することを意味します。 たとえば、cx_all は、2つのブロックの対応する論理量子ビット間に CNOT を適用します。

すべてのコードがすべての候補命令をサポートしているわけではありません。 strict=True (既定値) では、ビルダーは候補をビルドして検証できない場合にエラーを発生させます。 strict=False では、ビルダーはサポートされている命令を保持し、省略したものを protocol.metadata["qdk.ec"]["build"]["omitted"] に記録します。

論理レイヤーとそのガジェットを調べます。

logical_layer = protocol.layers[0]

print("Instructions:")
for mnemonic in logical_layer.instruction_set.instructions:
    print(" -", mnemonic)

print("Gadgets:")
for mnemonic in logical_layer.gadgets:
    print(" -", mnemonic)

2 番目のレイヤーには、生成された回路で使用される物理 Stim の命令セットが含まれています。

ガジェットを検査する

論理命令はコントラクトを宣言します。 そのガジェットは、コントラクトを実装する回路を与えます。

prepare_z_all ガジェットを確認します。

gadget = logical_layer.gadgets["prepare_z_all"]
gadget_profile = ec.GadgetProfile(gadget)

print("Declared objective:")
print(gadget_profile.objective)

print("Computed action:")
print(gadget_profile.action)

回路から計算されたアクションを、宣言された命令目標と比較します。

objective = gadget_profile.objective
assert objective is not None

if gadget_profile.action.is_equivalent_to(objective):
    print("The gadget implements its declared instruction.")
else:
    print(objective.why_not_equivalent_to(gadget_profile.action))

GadgetProfileは入力のスナップショットを取得するため、後の変更は反映されません。 ガジェットを編集したら、新しいプロファイルを作成してください。

プロトコルを監査する

auditを使用して、qodec全体を確認します。

report = ec.audit(protocol)
print(report)

監査でエラーや警告が検出されない場合は、次の情報が出力されます。

audit: ok

監査では、次の領域がチェックされます。

  • 符号の代数
  • 命令とレイヤー構造
  • 回路呼び出しとパラメーターのバインド
  • ガジェットの操作
  • チェックと読み出しの式
  • フラグの式
  • 入力フレームと出力フレームの関係
  • 参照境界とプロトコルの完全性

特定の重大度に対応するには、レポート コレクションを使用します。

if report.errors:
    for diagnostic in report.errors:
        print(diagnostic)
    raise ValueError("The qodec has audit errors.")

if report.warnings:
    for diagnostic in report.warnings:
        print(diagnostic)

report.ok値は、レポートにエラーがないことを意味します。 警告によってそのプロパティが変わることはありません。

不足している数式を導出する

ドラフト ガジェットまたは qodec が、正確なシミュレーションで導出できるチェックや論理的読み出しの式を省略している場合は、filled を使用します。

次の例では、プロトコルの不完全なコピーを作成します。

draft = qodec.Qodec.loads(protocol.dumps())
draft_gadgets = draft.layers[0].gadgets

draft_gadgets["measure_x_all"].readouts = []
draft_gadgets["cx_all"].checks = []

print(ec.audit(draft))

サポートされている式を入力し、結果の監査を行います。

completed = ec.filled(draft)
print(ec.audit(completed))

filled関数は、新しいガジェットまたはqodecを返し、入力を変更しません。 チェック、観測可能な読み出しの式、およびフレーム関係を導出します。 ノイズレス シミュレーションではフラグが検出する必要がある障害を識別できないため、フラグ バインドが保持されます。

元のプロトコルに既に検証済みの直接方程式がある場合、filled は可能な限りそれらを保持します。

ガジェットの障害を分析する

エラーのない監査は、ノイズレスの一貫性を証明します。 それはフォールトトレランスを証明するものではありません。

GadgetProfile.distanceを呼び出して、宣言されたすべてのチェックとフラグが 0 のままで論理障害を引き起こす、許容される回線障害の最小数を見つけます。 また、その組み合わさったフォールトによって、各出力が符号空間内にとどまる必要があります。

protocol = completed

for mnemonic, gadget in sorted(protocol.layers[0].gadgets.items()):
    result = ec.GadgetProfile(gadget).distance()
    witness = result.witness if result.upper_bound is not None else "none"
    print(f"{mnemonic}: distance={result}, witness={witness}")

既定のフォールト モデルには、各回線呼び出し後に次のイベントが含められます。

  • 呼び出しが作用する量子ビットの Pauli エラー
  • 呼び出しによって生成される読み出しビットの反転
  • これらの関連するエラーの組み合わせ

1 回の呼び出しでの各イベントは、多量子ビットに相関したエラーがある場合でも、コストが 1 として数えられます。

コードは距離 2 を持つことができますが、そのガジェットの1つは距離1です。 たとえば、単一の回路故障が補助量子ビットを介して広がり、検出されない論理エラーが発生する可能性があります。 チェックを追加するか、回線にフラグを付けてから、新しい GadgetProfile を作成し、距離をもう一度計算します。

特定のフォールトを検査する

FaultEvent.after を使用して、0 を基点とする回線呼び出しインデックスの後にフォールトを定義します。

gadget = protocol.layers[0].gadgets["syndrome"]
profile = ec.GadgetProfile(gadget)

fault = ec.FaultEvent.after(0, ec.Pauli("X_0"))
(effect,) = profile.effects_of([fault])

print("Fault:", fault)
print("Changed quantities:", effect)

FaultEffect には、変更されたチェック、論理読み出し、出力フレームの符号を含めることができます。 既定の回線レベル モデルがハードウェアの想定と一致しない場合は、明示的な fault set を使用します。

result = profile.distance(faults=[fault])
print(result)

既定の障害セットは各呼び出しでサポートされる範囲ごとに指数関数的に増加するため、正確なガジェット検索には時間がかかる場合があります。 検索を制限する必要がある場合は、 distance_bounds、明示的な障害セット、または upper_bound を使用します。

プロトコルを保存する

保存する前に、最終的な qodec を確認します。

report = ec.audit(protocol)
if report.errors or report.warnings:
    raise ValueError(str(report))

プロトコルを 1 つの YAML バンドルとして保存し、再度読み込んで元のプロトコルと一致することを確認します。

from pathlib import Path

output_directory = Path("qodec-output")
output_directory.mkdir(exist_ok=True)

saved_path = protocol.save(str(output_directory), single_file=True)
loaded = qodec.Qodec.load(saved_path)

assert loaded == protocol
print("Saved qodec:", saved_path)

シングル ファイル バンドルでは、マニフェスト、コード定義、命令セット、ガジェット、および回路ソースが 1 つの YAML ファイルに格納されるため、共有と再読み込みを簡単に行えるようなります。

プロトコルを実行する

qodec が監査に合格し、距離の要件を満たしたら、論理プログラムを量子中間表現 (QIR) にコンパイルし、プロトコルを通じて実行します。 論理プログラムを実行してデコードするを参照してください。

フラグの回線を追加し、論理エラー率を比較する完全な例については、QDK GitHub リポジトリの「QEC プロトコルの詳細手順」を参照してください。