QuTiPのdimsを習得して量子系を正確に記述しようの巻

## はじめに
QuTiPでは量子力学の状態空間の元やその空間に作用する演算子(行列)などはQobjというクラスのインスタンスとして定義されて、種々の便利な演算が実装されている。Qobjクラスには考えている量子系の状態空間のテンソル構造に関する情報を担っているdimsというプロパティがあり、初めてQuTiPを扱う時にはこのプロパティが原因となってエラーに遭遇してしまうことがある。一見邪魔に思えるプロパティではあるものの、この情報を保持することによって計算ミスも減る上に部分トレースなどの量子系特有の操作も容易に扱える様に頑張ってくれているプロパティである。このプロパティに慣れ親しむことで、より快適なQuTiPライフを謳歌しよう。

## 仕様を知る
ここではdimsの仕様を理解するために「行列におけるdims」と「状態ベクトルにおけるdims」の2つについて考える。両者はプログラム上はほとんど同じ問題ではあるものの、状態ベクトルにおけるdimsの方が少し理解しにくいかもしれないので、ここでは段階を追って説明することにする。

### 例： 行列におけるdims
まずは、例として3qubit系に作用する単位行列を作ることを考える。3qubit系の状態空間は各qubitの状態空間である複素2次元空間のテンソル積として与えることができるので、数学的には複素8次元空間と等価(Hilbert空間として同型)である。そこで、以下の様に2通りで複素8次元空間に作用する単位行列を定義してみる。

```py:素朴な定義
qeye(8)
# Quantum object: dims = [[8], [8]], shape = (8, 8), type = oper, isherm = True
# Qobj data =
# [[1. 0. 0. 0. 0. 0. 0. 0.]
#  [0. 1. 0. 0. 0. 0. 0. 0.]
#  [0. 0. 1. 0. 0. 0. 0. 0.]
#  [0. 0. 0. 1. 0. 0. 0. 0.]
#  [0. 0. 0. 0. 1. 0. 0. 0.]
#  [0. 0. 0. 0. 0. 1. 0. 0.]
#  [0. 0. 0. 0. 0. 0. 1. 0.]
#  [0. 0. 0. 0. 0. 0. 0. 1.]]
```

```py:物理的描像と一貫したテンソル積による定義
tensor(qeye(2), qeye(2), qeye(2))
# Quantum object: dims = [[2, 2, 2], [2, 2, 2]], shape = (8, 8), type = oper, isherm = True
# Qobj data =
# [[1. 0. 0. 0. 0. 0. 0. 0.]
#  [0. 1. 0. 0. 0. 0. 0. 0.]
#  [0. 0. 1. 0. 0. 0. 0. 0.]
#  [0. 0. 0. 1. 0. 0. 0. 0.]
#  [0. 0. 0. 0. 1. 0. 0. 0.]
#  [0. 0. 0. 0. 0. 1. 0. 0.]
#  [0. 0. 0. 0. 0. 0. 1. 0.]
#  [0. 0. 0. 0. 0. 0. 0. 1.]]
```

一見同じ行列が生成されたかの様に見えるが、出力をよくみると前者は```dims = [[8], [8]]```となっているのに対して、後者は```dims = [[2, 2, 2], [2, 2, 2]]```となっていることがわかる。それぞれの右辺にある値はdimsが記録しているテンソル積の構造であり、```[[縦方向のテンソル積構造], [横方向のテンソル積構造]]```という形式をとっている。このdimsはあくまでインスタンスのメモ書き程度のものだと思うかもしれないが、QuTiPはこの情報を重視しており、例えば、上で定義したdimsの構造が異なる2つの行列を足そうと試みると

```py:異なるdimsのQobjを足してみる
qeye(8) + tensor(qeye(2), qeye(2), qeye(2))
# TypeError: Incompatible quantum object dimensions
```

の様にエラーが出てしまい、計算することはできない。したがって、dimsを正しく扱うことがQuTiPを使いこなす上では不可欠である。

### 例： 状態ベクトルにおけるdims
行列の時と同じ様に3qubit系での状態$|000\rangle$を表す8成分ベクトルを記述することを試みよう。ベクトルについても以下の様な2通りの定義の仕方が考えられるだろう。

```py:素朴な定義
basis(8, 0)
# Quantum object: dims = [[8], [1]], shape = (8, 1), type = ket
# Qobj data =
# [[1.]
#  [0.]
#  [0.]
#  [0.]
#  [0.]
#  [0.]
#  [0.]
#  [0.]]
```

```py:物理的描像と一貫したテンソル積による定義
tensor(basis(2, 0), basis(2, 0), basis(2, 0))
# Quantum object: dims = [[2, 2, 2], [1, 1, 1]], shape = (8, 1), type = ket
# Qobj data =
# [[1.]
#  [0.]
#  [0.]
#  [0.]
#  [0.]
#  [0.]
#  [0.]
#  [0.]]
```

行列の時と同様に、```dims```に格納されている値が異なることがわかる。先程と同様に、これらのベクトルは足すことすらできない。

```py:異なるdimsのQobjを足してみる
basis(8, 0) + tensor(basis(2, 0), basis(2, 0), basis(2, 0))
# TypeError: Incompatible quantum object dimensions
```

頭のいい人には思いつかない疑問かもしれないが、僕は3秒ほど```dims = [[2, 2, 2], [1, 1, 1]]```の意味がよくわからなかった。と言うのも、ベクトルなのに横の成分が```[1]```ではなく```[1, 1, 1]```であることが何を意味しているのかがわからなかった。せっかくなのでその疑問の自分なりの解決をここに記しておく。
まず、状態ベクトル(ケットベクトル, 縦ベクトル)と言うのは、内積を通じてブラベクトル(横ベクトル)を受け取ってスカラーを返す関数として捉えることができる。例えば、$|a\rangle\otimes|b\rangle\otimes|c\rangle$というケットベクトルのブラベクトル$\langle{x}|\otimes\langle{y}|\otimes\langle{z}|$への作用は

```math
\langle{x}|\otimes\langle{y}|\otimes\langle{z}|~~\mapsto~~\langle{x}|\otimes\langle{y}|\otimes\langle{z}|\cdot|a\rangle\otimes|b\rangle\otimes|c\rangle=\langle{x}|a\rangle\langle{y}|b\rangle\langle{z}|c\rangle
```

と表される。ここで注目するべきは最右辺はもちろん$\mathbb{C}$の元であるものの、同時に$\mathbb{C}\otimes\mathbb{C}\otimes\mathbb{C}$の元の様にも見えるということである。(数学的には$\mathbb{C}\otimes\mathbb{C}\otimes\mathbb{C}\cong\mathbb{C}$であることと対応している。)したがって、QuTiPの人々はテンソル積空間の結果を上式の最右辺の様なイメージで捉えているのだと思われる。
これで一件落着かと思いきや、実はまだ疑問になりうる点がある。例えば8準位系の元$|\chi\rangle=|0\rangle$と3qubit系の元$|000\rangle$を用いて$|\chi\rangle\langle{000}|$の様な演算子を定義したい時に$|\chi\rangle$は```dims = [[8], [1]]```であるのにもかかわらず、$\langle{000}|$は```dims = [[1, 1, 1], [2, 2, 2]]```であるので単純に掛け算するだけでは再び```TypeError: Incompatible quantum object dimensions```の様なエラーを返されるのではないかと言うことだ。考えるよりも実行してみた方が早いと言うことで実行してみると、

```py:テンソル積構造の異なるケットとブラの背中合わせ演算をしてみた
basis(8, 0) * (tensor(basis(2, 0), basis(2, 0), basis(2, 0))).dag()
# Quantum object: dims = [[8], [2, 2, 2]], shape = (8, 8), type = oper, isherm = True
# Qobj data =
# [[1. 0. 0. 0. 0. 0. 0. 0.]
#  [0. 0. 0. 0. 0. 0. 0. 0.]
#  [0. 0. 0. 0. 0. 0. 0. 0.]
#  [0. 0. 0. 0. 0. 0. 0. 0.]
#  [0. 0. 0. 0. 0. 0. 0. 0.]
#  [0. 0. 0. 0. 0. 0. 0. 0.]
#  [0. 0. 0. 0. 0. 0. 0. 0.]
#  [0. 0. 0. 0. 0. 0. 0. 0.]]
```
となり、しっかりと計算できた。と言うわけで、この疑問は僕の杞憂であってしっかり問題なく実装されていることがわかったので、これにてベクトルに対するdims問題は全て解決である。