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.

Vectorのスライシング

タグ: tutorial

チュートリアル02ではパラメータ付き量子カーネルの構築方法と、単一量子ビットゲートのブロードキャスト機能を紹介しました。本章では、より複雑な量子カーネルを記述するのに役立つQamomileの機能、スライシングを紹介します。

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

from qamomile.circuit.transpiler.errors import (
    AffineTypeError,
    QubitBorrowConflictError,
    UnreturnedBorrowError,
)

スライスの基本構文

Vector[Qubit]をPythonのslice(start:stop:step)でインデックスすると、VectorViewが返されます。これは親Vectorの部分範囲を指すハンドルですが、ほとんどのケースでは通常のVector[Qubit]と同じように扱えます。

@qmc.qkernel
def demo() -> qmc.Vector[qmc.Bit]:
    q = qmc.qubit_array(6, name="q")
    # スライスで偶数番目の量子ビットを取り出す
    evens = q[0::2]
    # スライスで奇数番目の量子ビットを取り出す
    odds = q[1::2]
    # 取り出した量子ビット配列のshapeをもとにループし、Hゲートを適用する
    for i in qmc.range(evens.shape[0]):
        evens[i] = qmc.h(evens[i])
    # ブロードキャスト機能によりスライス全体にXゲート(単一量子ビットゲート)を適用する
    odds = qmc.x(odds)
    # 元の量子ビット配列にスライスを戻す
    q[0::2] = evens
    q[1::2] = odds
    return qmc.measure(q)


demo.draw(fold_loops=False)
<Figure size 600x496 with 1 Axes>

demoで注目すべき点:

  • evens = q[0::2]qの偶数番目の量子ビットを覆うVectorViewを作成します。同様にodds = q[1::2]qの奇数番目の量子ビットを覆うVectorViewを作成します。

  • ループ内のevens[i] = qmc.h(evens[i])はチュートリアル02で見たアフィン型のパターンと同じで、各要素は消費され、新しい要素ハンドルがその位置に格納されます。

  • VectorViewVector[Qubit]とほとんど同じように使用できます。

インライン短縮形

本体がブロードキャスト可能な単一の操作なら、VectorViewに名前を付ける必要はありません。借りる、変換する、返すを1ステートメントで完結できます。

@qmc.qkernel
def demo_inline() -> qmc.Vector[qmc.Bit]:
    q = qmc.qubit_array(6, name="q")
    q[0::2] = qmc.h(q[0::2])
    q[1::2] = qmc.x(q[1::2])
    return qmc.measure(q)


demo_inline.draw(fold_loops=False)
<Figure size 600x496 with 1 Axes>

ネストしたスライス

VectorViewそのものをさらにスライスし、新たなVectorViewを取り出すこともできます。結果は親VectorViewの部分範囲を覆うVectorViewとなります。各階層は1つ上の階層から借り、各階層は祖父にあたる階層に触る前に直接の親に返さなければなりません。

例えば、outer = q[0::2]inner = outer[1:3]があるとき、返却順序はinner → outer → rootです。まずinnerouterに、次にouterqに返します。

@qmc.qkernel
def nested_slice() -> qmc.Vector[qmc.Bit]:
    q = qmc.qubit_array(6, name="q")
    outer = q[0::2]
    inner = outer[1:3]
    for i in qmc.range(inner.shape[0]):
        inner[i] = qmc.h(inner[i])
    outer[1:3] = inner
    q[0::2] = outer
    return qmc.measure(q)


nested_slice.draw(fold_loops=False)
<Figure size 600x496 with 1 Axes>

VectorViewをヘルパーカーネルに渡す

VectorViewは外部の量子カーネルに渡されるときはVector[Qubit]と同様に扱われます。

@qmc.qkernel
def h_all(v: qmc.Vector[qmc.Qubit]) -> qmc.Vector[qmc.Qubit]:
    return qmc.h(v)


@qmc.qkernel
def x_all(v: qmc.Vector[qmc.Qubit]) -> qmc.Vector[qmc.Qubit]:
    return qmc.x(v)


@qmc.qkernel
def demo_helper() -> qmc.Vector[qmc.Bit]:
    q = qmc.qubit_array(6, name="q")
    evens = q[0::2]
    odds = q[1::2]
    evens = h_all(evens)
    odds = x_all(odds)
    q[0::2] = evens
    q[1::2] = odds
    return qmc.measure(q)


demo_helper.draw(inline=True, fold_loops=False)
<Figure size 850.5x828 with 1 Axes>

これはqamomile.circuit.stdlibの組み込み関数にも適用されます。qmc.qftqmc.iqftqmc.qpeはいずれもVector[Qubit]を取り、VectorViewに対しても同様に動きます。

@qmc.qkernel
def qft_on_window() -> qmc.Vector[qmc.Bit]:
    q = qmc.qubit_array(6, name="q")
    q[1:4] = qmc.qft(q[1:4])
    return qmc.measure(q)


qft_on_window.draw()
<Figure size 631.5x496 with 1 Axes>

スライシングによる量子ビット配列の分割

Qamomileでは、量子ビット配列Vector[Qubit]そのものを分割したり統合したりすることはできません。一方で、スライシング機能を駆使すると、一つの大きな量子ビット配列から複数のVectorViewに分けて取り出すことができ、あたかも複数のレジスタがあるかのようにアルゴリズムを書けます。ここでは、量子ビット全体に量子フーリエ変換をかけたあと、その一部にだけ再度量子フーリエ変換をかけてみます。Qamomileの量子フーリエ変換qftVector[Qubit]を引数に取るため、もしスライシング機能がなければ、量子フーリエ変換自体を量子カーネルの中にゼロから書く必要があります。スライシングを使って量子ビット配列の一部を取り出せば、この手間を回避できます。

@qmc.qkernel
def demo_separation() -> qmc.Vector[qmc.Bit]:
    qs = qmc.qubit_array(6, name="qs")
    # 量子フーリエ変換を全体にかける
    qs = qmc.qft(qs)
    # 前半にだけ再度量子フーリエ変換をかける
    partial_qs = qs[0:3]
    partial_qs = qmc.qft(partial_qs)
    # 元の量子ビット配列にスライスを戻す
    qs[0:3] = partial_qs
    return qmc.measure(qs)


demo_separation.draw(inline=True, fold_loops=False)
<Figure size 774.5x496 with 1 Axes>

VectorViewのエラーパターン

VectorViewは、親となる量子ビット配列Vector[Qubit]からスライスで指定された量子ビットを借りることで作成されます。親となるVector[Qubit]から見ると、指定された量子ビットは貸し出された状態になるため、対象の量子ビットが返却されるまで、親となるVector[Qubit]から直接アクセスすることはできません。同様の理由で、親となるVector[Qubit]全体を消費するような操作も許されません。貸し出した量子ビットを返却するには、スライス代入を行う必要があります。

ここでは、貸し出した量子ビットを返さずにアクセスする例を通じてエラーパターンを見ていきます。

@qmc.qkernel
def direct_parent_access() -> qmc.Vector[qmc.Bit]:
    q = qmc.qubit_array(6, name="q")
    v = q[1:3]
    q[1] = qmc.h(q[1])  # 貸出済みの量子ビットに直接アクセスしているためエラー
    q[1:3] = v
    return qmc.measure(q)


try:
    direct_parent_access.draw()
except QubitBorrowConflictError as e:
    print(f"Error type: {type(e).__name__}")
    print(f"Error message: {e}")
else:
    raise AssertionError(
        "expected QubitBorrowConflictError, but draw() returned normally"
    )
Error type: QubitBorrowConflictError
Error message: Parent slot 'q[1]' is currently held by a VectorView slice.
Access it through the view, or let the view finish before touching the parent directly.
@qmc.qkernel
def forgot_return() -> qmc.Vector[qmc.Bit]:
    q = qmc.qubit_array(6, name="q")
    evens = q[0::2]
    for i in qmc.range(evens.shape[0]):
        evens[i] = qmc.h(evens[i])
    return qmc.measure(q)  # VectorViewを返却していないためエラー


try:
    forgot_return.draw()
except UnreturnedBorrowError as e:
    print(f"Error type: {type(e).__name__}")
    print(f"Error message: {e}")
else:
    raise AssertionError("expected UnreturnedBorrowError, but draw() returned normally")
Error type: UnreturnedBorrowError
Error message: Array 'q' has unreturned slice-view borrows from q[slice].
Borrowed slots: q[0] (held by slice view), q[2] (held by slice view), q[4] (held by slice view)

Fix: Return every active view via slice assignment before consuming the parent:
  view = q[a:b:c]
  # ... use view ...
  q[a:b:c] = view  # explicit return
@qmc.qkernel
def overlapping_views() -> qmc.Vector[qmc.Bit]:
    q = qmc.qubit_array(6, name="q")
    a = q[0:3]
    b = q[
        2:5
    ]  # aで貸し出した量子ビットを返却せずに二重に貸し出そうとしているためエラー
    q[0:3] = a
    q[2:5] = b
    return qmc.measure(q)


try:
    overlapping_views.draw()
except QubitBorrowConflictError as e:
    print(f"Error type: {type(e).__name__}")
    print(f"Error message: {e}")
else:
    raise AssertionError(
        "expected QubitBorrowConflictError, but draw() returned normally"
    )
Error type: QubitBorrowConflictError
Error message: Parent slot 'q[2]' is already owned by another slice view 'q[slice]' and cannot be re-sliced while that view is live.  Consume / release the existing view before slicing the same range again.
@qmc.qkernel
def invalid_nested_return() -> qmc.Vector[qmc.Bit]:
    q = qmc.qubit_array(6, name="q")
    outer = q[0::2]
    inner = outer[1:3]
    for i in qmc.range(inner.shape[0]):
        inner[i] = qmc.h(inner[i])
    q[0::2] = outer  # innerをouterに返却せずに、outerをrootに戻そうとしているためエラー
    return qmc.measure(q)


try:
    invalid_nested_return.draw()
except AffineTypeError as e:
    print(f"Error type: {type(e).__name__}")
    print(f"Error message: {e}")
else:
    raise AssertionError("expected AffineTypeError, but draw() returned normally")
Error type: AffineTypeError
Error message: Slice assignment RHS view 'q[slice]' no longer owns 'q[2]'; the parent's borrow record points at an unrelated view / element.  This view was likely drained by a later overlapping slice; reconstruct the view before assigning it back.

次へ: 制御ゲートqmc.controlによるビルトインゲートやサブカーネルの制御、concrete/symbolicの制御数の指定、合成できないパターンのカタログを扱います。