ボソン回路の扱い方
BosonicCircuitは、計算をボソンモード上で直接表現します。トランケーションも、エンコーディングも、量子ビット数も持ちません。これらはトランスパイルの際に後から入ってくるため、1つの回路を作り直すことなく、複数のエンコーディングや複数のトランケーションのもとで下位に変換できます。
QiskitのQuantumCircuitと比べて意図的に縮小されたAPIを公開しているため、量子ビットゲートをボソンモードに適用することはできません。そのような操作は明確に定義されていないからです。このガイドでは、小さな回路からより大きな回路を組み立てるために公開されている操作を扱います。
ボソン回路を使う理由
ボソン・ハミルトニアンの時間発展を量子ビットベースのアーキテクチャーに載せる従来のワークフローを考えてみましょう。
-
ボソン・ハミルトニアンを定義します。たとえば です。
-
トランケーションとエンコーディングを選び、それらを使ってハミルトニアンを量子ビット演算子にマッピングします。
-
トロッター化またはその他の方法で、量子ビット空間での時間発展を実装します。
2番目のステップは早い段階に来て、取り消すのにコストがかかる2つの決定を下します。トランケーションは各モードが保持するレベルの数を決め、エンコーディングはそれに必要な量子ビット数を決めるため、回路に対して他の何かを行う前に、どちらも量子ビット演算子の中で固定されます。同じ物理を別のカットオフや別のエンコーディングで見るには、ハミルトニアンを再度マッピングし、回路を再度構築します。さらに、マッピングで失われるのはボソンの構造、つまりどの項が一緒に属するか、そしてホッピング対の項が互いの随伴であることです。
BosonicCircuitは、それらの決定を最後に移します。
-
ボソン・ハミルトニアンを定義します。
-
ボソンのゲートを使って、時間発展をボソンモード上で直接表現します。
-
トランスパイルの一部として、トランケーションとエンコーディングを選びます。
回路は何を計算するかを記述し、トランスパイルはそれを量子ビット上にどのように載せるかを決めます。同じ回路は、あらゆるカットオフとあらゆるエンコーディングのもとで下位に変換され、演算子のグループ化も、それを利用できるパスのために残ります。次のセクションでは、そのような回路の組み立て方を示します。ボソン回路のトランスパイルでは、それが下位に変換される様子を示します。
汎用的なモードのインデックス付け
operatorsモジュールと回路は、モードについて同じ規約を共有しています。モードはボソンの自由度を区別するための単なるインデックスであり、それ自体に意味はありません。したがって、OperatorTraitプロトコルを実装する任意の演算子を、それが何を表すかにかかわらず発展させることができ、回路はモード2が何を表すかを知る必要がありません。モードのサイズについても同様です。回路には占有数のカットオフがないため、ここではモードにいくつのレベルがあるかは示されません。
ゲートを追加する
ゲートはモードに追加されます。BosonicGateのインスタンスだけが受け付けられます。
>>> from qiskit_bosons.circuit import BosonicCircuit
>>> from qiskit_bosons.circuit.library import Evolution, InitializeModes
>>> from qiskit_bosons.operators import BosonOperator, ann, cre
>>>
>>> hopping = BosonOperator.from_dict({(cre(0), ann(1)): -1.0, (cre(1), ann(0)): -1.0})
>>> hopping.groups = [0] * len(hopping) # the pair is Hermitian, its terms alone are not
>>>
>>> prepare = BosonicCircuit(2)
>>> prepare.append(InitializeModes([2, 0]), prepare.modes)
>>> step = BosonicCircuit(2)
>>> step.append(Evolution(2, hopping, time=0.1), step.modes)
それ以外はすべて拒否され、エラーには問題のある型の名前が示されます。
>>> from qiskit.circuit.library import HGate
>>> try:
... prepare.append(HGate(), [0])
... except ValueError as error:
... print(error)
Unsupported instruction type: <class '...HGate'>
回路を合成する
compose()は、ある回路の命令を別の回路の上に配置します。命令は単一のゲートにまとめられるのではなくインライン展開されるため、結果は、分解やトランスパイルを続けられる通常の回路になります。
>>> program = prepare.compose(step)
>>> program.draw("mpl", fold=-1)
<Figure size ... with 1 Axes>
デフォルトでは新しい回路が返され、両方のオペランドはそのまま残ります。最初のものを変更するにはinplace=Trueを、オペランドを既存の命令の前に置くにはfront=Trueを渡します。
>>> step.compose(prepare, front=True)._inner.data == program._inner.data
True
より狭い回路は、モードの選ばれた部分集合の上に配置できます。
>>> single = BosonicCircuit(1)
>>> single.append(Evolution(1, BosonOperator.from_dict({(cre(0), ann(0)): 1.0}), 0.1), single.modes)
>>> wide = BosonicCircuit(3).compose(single, [2])
>>> wide.draw("mpl", fold=-1)
<Figure size ... with 1 Axes>
オペランドとして受け付けられるのは別のBosonicCircuitだけです。Qiskit自身のcomposeは量子ビットゲートをインライン展開し、appendが強制するチェックを回避してしまうからです。
回路を繰り返す
repeat()は回路を端から端まで繰り返します。繰り返しは平坦化されます。結果は、回路を包む1つのゲートではなく、命令をreps回順に並べたものになります。したがって、演算の数はrepsに比例して増え、decomposeは繰り返しを1つ取り除くのではなく、繰り返された本体に作用します。
>>> len(step.repeat(0)._inner.data) # zero repetitions is the identity
0
>>> step.repeat(5).draw("mpl", fold=-1)
<Figure size ... with 1 Axes>
これは回路レベルでトロッター化する方法です。短い時間だけ発展させて、繰り返します。ゲート自体に積公式を選ぶ代替手段と、この2つが「繰り返し回数」という言葉を共有しているものの異なるものである理由については、ボソン空間でのEvolutionゲートの合成を参照してください。
バリアでステップを分ける
バリアは同期点を示し、最適化パスはそれをまたいでゲートを融合しません。barrier()は、すべてのモード、または指定したモードに1つ配置します。
>>> program = BosonicCircuit(2)
>>> program.append(InitializeModes([2, 0]), program.modes)
>>> program.barrier()
>>> program.barrier(program.modes[0], label="zero")
>>> program.draw("mpl", fold=-1)
<Figure size ... with 1 Axes>
repeat(..., insert_barriers=True)は、連続する繰り返しの間に1つ配置し、最後の後には配置しません。
>>> step.repeat(3, insert_barriers=True).draw("mpl", fold=-1)
<Figure size ... with 1 Axes>
バリアとエンコーディング
エンコーディングは1モードにつき複数の量子ビットを必要とするため、モードのレジスターと量子ビットのレジスターはサイズが異なり、レイアウトはレジスター全体を対応づけます。したがって、モードにはバリアを絞り込むための固有の量子ビットがありません。ボソンから量子ビットへの合成の際、バリアはそれが触れていた各レジスターのすべての量子ビットにマッピングされ、これはどのレイアウトでも正しい結果になります。
>>> from qiskit_bosons.mappers import LevelSpec
>>> from qiskit_bosons.transpiler.presets import generate_preset_occupation_pass_manager
>>> lowered = generate_preset_occupation_pass_manager(LevelSpec(2, "unary_projected")).run(program)
>>> lowered.draw("mpl", fold=-1)
<Figure size ... with 1 Axes>
ボソン回路を読み出す
BosonicCircuitにはmeasureもmeasure_allもありません。測定されたビットが何を意味するかはエンコーディングに依存します。ユナリー・エンコーディングではモードの占有数が複数の量子ビットに広がっており、ビットのパターンから読み取る必要があります。コンパクトなエンコーディングでは2進数になります。そのため、モードを指定する測定はエンコーディングを知っている必要がありますが、ボソン回路は意図的にそれを知りません。代わりに、トランスパイルされたQuantumCircuitを測定し、ビット列をLevelEncoding.level_to_bits()と、下位変換に使ったLevelSpecのオフセットでデコードします。エンコーディングを選択してボース・ハバード動力学をシミュレートするでは、完全なモデルでこれを行っています。
>>> hasattr(BosonicCircuit, "measure_all")
False
次のステップ
-
ボソン回路のトランスパイルでは、ボソン回路が量子ビット回路になる過程を段階ごとに説明します。
-
ボソンモードの回転では、モードを混合しボソン数を保存するゲートを扱います。
-
ボソン空間でのEvolutionゲートの合成では、ボソン空間で時間発展をトロッター化する方法を示します。
-
演算子表現の設計原則では、ゲートが保持する演算子を説明します。