Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

古典制御フローパターン

量子回路の構造は古典制御フローに依存することが多くあります。量子ビットのイテレーション、グラフのエッジに基づくゲート適用、ゲート列の条件分岐などです。Qamomileではqmc.rangeqmc.itemsif分岐、whileループでこれらをサポートしています。

この章では以下を扱います:

  • qmc.range()によるループ

  • qmc.items()による辞書のイテレーション

  • 測定結果に対するif / whileによる回路途中の分岐

# 最新のQamomileをpipからインストールします!
# !pip install qamomile
import qamomile.circuit as qmc
from qamomile.qiskit import QiskitTranspiler

transpiler = QiskitTranspiler()

qmc.rangeループ

qmc.rangestartstopstepを引数に取ることができます。ここでは偶数番目の量子ビットにHゲートを適用し、隣接ペアをCXでエンタングルする量子カーネルを作ってみます。

@qmc.qkernel
def hadamard_chain(n: qmc.UInt) -> qmc.Vector[qmc.Bit]:
    q = qmc.qubit_array(n, name="q")

    # 偶数番目の量子ビットに H を適用
    for i in qmc.range(0, n, 2):
        q[i] = qmc.h(q[i])

    # 隣接するペアをエンタングル
    for i in qmc.range(n - 1):
        q[i], q[i + 1] = qmc.cx(q[i], q[i + 1])

    return qmc.measure(q)
hadamard_chain.draw(n=5, fold_loops=False)
<Figure size 975.5x416 with 1 Axes>

qmc.itemsによるスパースな相互作用データの処理

QAOAやVQEなど多くの量子アルゴリズムでは、グラフや相互作用マップで決まる特定の量子ビットペアにのみゲートを適用します。全ペアをループするのではなく、相互作用の辞書を渡してqmc.items()でイテレーションできます。

辞書型にはQamomileのシンボリック型を使います:qmc.Dict[qmc.Tuple[qmc.UInt, qmc.UInt], qmc.Float] — キーが量子ビットインデックスのペア、値が相互作用の重みです。

@qmc.qkernel
def sparse_coupling(
    n: qmc.UInt,
    edges: qmc.Dict[qmc.Tuple[qmc.UInt, qmc.UInt], qmc.Float],
    gamma: qmc.Float,
) -> qmc.Vector[qmc.Bit]:
    q = qmc.qubit_array(n, name="q")

    # 初期重ね合わせ
    for i in qmc.range(n):
        q[i] = qmc.h(q[i])

    # 指定されたエッジにのみ RZZ 相互作用を適用
    for (i, j), weight in qmc.items(edges):
        q[i], q[j] = qmc.rzz(q[i], q[j], gamma * weight)

    return qmc.measure(q)

transpiler.to_circuit()による確認

draw()は全パターン(特に複雑な型を伴うitemsifwhile)にはまだ対応していません。そのような場合はtranspiler.to_circuit()で全パラメータをバインドした後のトランスパイル済みの回路を確認してください。

edge_data = {(0, 1): 1.0, (1, 2): -0.7, (0, 2): 0.3}

circuit = transpiler.to_circuit(
    sparse_coupling,
    bindings={"n": 3, "edges": edge_data, "gamma": 0.4},
)
print(circuit)
     ┌───┐                                    ┌─┐   
q_0: ┤ H ├─■─────────────────────■────────────┤M├───
     ├───┤ │ZZ(0.4)              │         ┌─┐└╥┘   
q_1: ┤ H ├─■─────────■───────────┼─────────┤M├─╫────
     ├───┤           │ZZ(-0.28)  │ZZ(0.12) └╥┘ ║ ┌─┐
q_2: ┤ H ├───────────■───────────■──────────╫──╫─┤M├
     └───┘                                  ║  ║ └╥┘
c: 3/═══════════════════════════════════════╩══╩══╩═
                                            1  0  2 

edge_dataの3つのエッジのみがRZZゲートを生成します。

if分岐とwhileループ

Qamomileは回路途中での測定に続く古典分岐をサポートしています。条件は測定結果Bit)でなければならず、量子カーネルの引数は使えません。

これはハードウェアレベルの条件付き実行に直接対応します:量子ビットを測定し、その結果に基づいて次の操作を決定します。

測定結果に対するif

よくあるパターンとして、ある量子ビットを測定し、その結果に基づいて別の量子ビットにゲートを条件付きで適用します。

@qmc.qkernel
def conditional_flip() -> qmc.Bit:
    q0 = qmc.qubit("q0")
    q1 = qmc.qubit("q1")

    q0 = qmc.x(q0)  # |1⟩ を準備
    bit = qmc.measure(q0)

    # q0 の測定結果に基づいて q1 を条件付きで反転
    if bit:
        q1 = qmc.x(q1)
    else:
        pass

    return qmc.measure(q1)

これはQiskitのif_else命令にトランスパイルされ、実行できます:

exe = transpiler.transpile(conditional_flip)
executor = transpiler.executor()
job = exe.sample(executor, bindings={}, shots=100)
result = job.result()
for value, count in result.results:
    print(f"  bit={value}: {count} shots")
  bit=1: 100 shots

q0は |1⟩ として準備されているため、測定結果は常に1となり、q1は常に反転されます。全てのショットで1が返るはずです。

測定結果に対するwhile

whileループは測定条件がfalseになるまで繰り返します。これはrepeat-until-successプロトコルに有用です。

@qmc.qkernel
def repeat_until_zero() -> qmc.Bit:
    q = qmc.qubit("q")
    q = qmc.h(q)  # |0⟩ か |1⟩ が 50/50 の確率
    bit = qmc.measure(q)

    while bit:
        # 0 が得られるまで再準備と再測定を繰り返す
        q = qmc.qubit("q2")
        q = qmc.h(q)
        bit = qmc.measure(q)

    return bit

これはQiskitのwhile_loop命令にトランスパイルされます。生成された回路構造を確認できます:

exe_while = transpiler.transpile(repeat_until_zero)
qc_while = exe_while.compiled_quantum[0].circuit
print(qc_while)
     ┌───┐┌─┐                             
q_0: ┤ H ├┤M├─────────────────────────────
     └───┘└╥┘┌───────── ┌───┐┌─┐ ───────┐ 
q_1: ──────╫─┤ While-0  ┤ H ├┤M├  End-0 ├─
           ║ └────╥──── └───┘└╥┘ ───────┘ 
           ║ ┌────╨────┐      ║           
c: 1/══════╩═╡ c_0=0x1 ╞══════╩═══════════
           0 └─────────┘      0           

ifwhileの組み合わせ

両方のパターンを組み合わせることができます。以下は測定を繰り返し行い、条件付きで補正ゲートを適用するプロトコルです:

@qmc.qkernel
def measure_and_correct() -> qmc.Bit:
    q0 = qmc.qubit("q0")
    q1 = qmc.qubit("q1")

    q0 = qmc.h(q0)
    bit = qmc.measure(q0)

    while bit:
        # bit が 1 なら q1 に補正を適用
        if bit:
            q1 = qmc.x(q1)
        else:
            q1 = q1
        # 再準備と再測定
        q0 = qmc.qubit("q0_retry")
        q0 = qmc.h(q0)
        bit = qmc.measure(q0)

    return qmc.measure(q1)
exe_combined = transpiler.transpile(measure_and_correct)
qc_combined = exe_combined.compiled_quantum[0].circuit
print(qc_combined)
     ┌───┐┌─┐                                                                 »
q_0: ┤ H ├┤M├─────────────────────────────────────────────────────────────────»
     └───┘└╥┘┌─────────        ┌──────  ┌───┐┌────────  ───────┐     ───────┐ »
q_1: ──────╫─┤          ───────┤ If-1  ─┤ X ├┤ Else-1    End-1 ├────        ├─»
           ║ │ While-0  ┌───┐  └──╥───  └───┘└────────  ───────┘ ┌─┐  End-0 │ »
q_2: ──────╫─┤          ┤ H ├─────╫──────────────────────────────┤M├        ├─»
           ║ └────╥──── └───┘     ║                              └╥┘ ───────┘ »
           ║ ┌────╨────┐     ┌────╨────┐                          ║           »
c: 3/══════╩═╡ c_0=0x1 ╞═════╡ c_0=0x1 ╞══════════════════════════╩═══════════»
           0 └─────────┘     └─────────┘                          0           »
«        
«q_0: ───
«     ┌─┐
«q_1: ┤M├
«     └╥┘
«q_2: ─╫─
«      ║ 
«c: 3/═╩═
«      2 

まとめ

  • qmc.range(n)でシンボリック範囲のループ。

  • qmc.items(dict)でスパースなキーバリューデータ(エッジ、重み)のイテレーション。

  • if bit: / while bit:測定結果に基づく分岐。両分岐で同じ量子ビットハンドルを扱う必要があります(アフィンルール)。

  • これらの制御フローは対象の量子SDKのネイティブな命令(例:Qiskitのif_elsewhile_loop)にトランスパイルされます。

次へ再利用パターン — ヘルパー量子カーネル、コンポジットゲート、スタブゲート。