Python: 超立方体

# はじめに

ここでは超立方体は単なるnビットで表現される2進数であるというお話をしようと思う。またまたぁｗっとなると思うので、実際にプログラムで4次元の正八胞体まで作成して可視化し、それを実感してもらおう。

``` python
import random
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
```

# 0次元

0次元は点である。今回はこの点から4次元まで拡張していき正八胞体を作成する。そのため、4次元分あらかじめ空間を用意し、使用していない空間は全て0として落としておく。まず0次元なので、すべての次元が0となっている。この点を2次元平面上から見ると以下のような感じ。

``` python
# dot = [1次元目, 2次元目, 3次元目, 4次元目] <- こんな感じです
dot = [[0], [0], [0], [0]]
plt.scatter(dot[0], dot[1])
plt.plot()
```

![0次元.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/365961/6d660c3e-3027-d8e8-3992-6d8c6a6037b5.png)

# 1次元

1次元は線である。最初の点は4次元まで拡張できるように作ってあるので、まず1次元目を拡張しよう。っと言っても拡張方法を定義しなければいけない。ということで関数として作ってしまいます。

## 拡張する関数

``` python
def extension(dim):
    """ 次元を拡張する関数

    :param dim: 拡張する次元の配列
    :returns: 拡張された配列
    """
    return np.concatenate([np.array(dim) - 1, np.array(dim) + 1]).tolist()
```

では作った関数で先ほどの0次元から拡張していきましょう！拡張すると要素の数は拡張前から2倍になるので、他の次元も2倍に（複製）しておきます。リストに*2すると各要素が複製されて倍になるよ(´っ･ω･)っ便利

``` python
# [1次元目 <- ここを拡張, 2次元目 <- 複製, 3次元目 <- 複製, 4次元目 <- 複製]
line = [extension(dot[0]), dot[1]*2, dot[2]*2, dot[3]*2]
plt.scatter(line[0], line[1])
plt.plot()
```

![1次元.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/365961/89c1a990-f730-4a8f-d8a8-e34751e6414c.png)

2つの点ができましたね、これを結ぶと直線になるわけですね(o'∀')ﾌﾑﾌﾑ


# 2次元

次は2次元目を拡張してみましょう。やる操作は1次元の時と同じで、2次元目に関数をかけて他の要素を2倍にするだけ。ということで、同じように可視化。

``` python
# [1次元目 <- 複製, 2次元目 <- ここを拡張, 3次元目 <- 複製, 4次元目 <- 複製]
square = [line[0]*2, extension(line[1]), line[2]*2, line[3]*2]
plt.scatter(square[0], square[1])
plt.plot()
```

![2次元.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/365961/92ecec21-9bb6-b705-a78b-e418c2457d74.png)

簡単.+.(ﾉ*･ω･)ﾉ*.

# 3次元

もういいですよね(´・ω・｀)同じようにやっちゃいます。ただし、平面上の可視化はさっきまでの2次元で限界なので…3次元上へのプロットを使って見やすくしておきます。（少し見ずらいかもしれませんが、じっくり見ると立方体の四隅に点が打たれている状態だと認識できますね）

``` python
# [1次元目 <- 複製, 2次元目 <- 複製, 3次元目 <- ここを拡張, 4次元目 <- 複製]
cube = [square[0]*2, square[1]*2, extension(square[2]), square[3]*2]
fig = plt.figure()
Axes3D(fig)
plt.plot(cube[0], cube[1], cube[2], marker="o", linestyle='None')
plt.show()
```

![3次元.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/365961/b524f319-0888-e997-5e82-0fc0464cdd3e.png)

# 4次元

さて、問題の4次元目ですが拡張はかんたんですよね？ではどのように可視化するかとなりますが…私は無理やり3次元空間上に可視化とかわけわからなくなってごまかされた気分になるので嫌いです。ということで、単にどんな配列になっているか見ていきましょう。すると得られる結果は以下のようになります。おやおや？何か見覚えありますね？(｡･Д･｡)、と感がいい人は気が付くかと思います。

``` python
# [1次元目 <- 複製, 2次元目 <- 複製, 3次元目 <- 複製, 4次元目 <- ここを拡張]
octachoron = [cube[0]*2, cube[1]*2, cube[2]*2, extension(cube[3])]
octachoron

>>> [[-1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1],
>>>  [-1, -1, 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, 1, 1],
>>>  [-1, -1, -1, -1, 1, 1, 1, 1, -1, -1, -1, -1, 1, 1, 1, 1],
>>>  [-1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1]]
```

この正八胞体を平行移動(+1)させてスケーリング(1/2)してみましょうかね。平行移動してスケーリングしても性質は変わらないので問題ありません。

``` python
octachoron_ = (np.array(octachoron) + 1) / 2
octachoron_

>>> array([[0., 1., 0., 1., 0., 1., 0., 1., 0., 1., 0., 1., 0., 1., 0., 1.],
>>>        [0., 0., 1., 1., 0., 0., 1., 1., 0., 0., 1., 1., 0., 0., 1., 1.],
>>>        [0., 0., 0., 0., 1., 1., 1., 1., 0., 0., 0., 0., 1., 1., 1., 1.],
>>>        [0., 0., 0., 0., 0., 0., 0., 0., 1., 1., 1., 1., 1., 1., 1., 1.]])

# 見やすく整数に変換
octachoron_.astype('i')

>>> array([[0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1],
>>>        [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1],
>>>        [0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1],
>>>        [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1]], dtype=int32)
```

　はい！正八胞体の頂点は4bitで表現する2進数の全パターンでしたとさ！ちゃんちゃん！っということで、私たちプログラマは平気で16次元の超立方体とか32次元の超立方体とか、64次元の超立方体とか使っちゃってるわけですねぇ～。なーんだ身近にあるじゃん！簡単簡単(´っ･ω･)っ
　っということで、暴論を言うと以下が16次元の超立方体を写像して見やすくした姿です！後はこれ2進数という空間に写像して、平行移動(-0.5)してスケーリング(2倍)すれば元の超立方体に戻って確かに！っと納得できます。

``` python
np.arange(2**16)

>>> array([    0,     1,     2, ..., 65533, 65534, 65535])
```

一応上のような配列からn次元超立方体を生成する変換を作成しておきます。ここまでの処理は分かりやすさを重視、以下はpythonらしさを重視してみました。結構綺麗にかけたと思いますが…指摘があれば(´・ω・｀)

``` python
# 次元数
n = 5

# 数列
nums = range(2**n)

# 2進数に写像
nbit = [[int(j) for j in format(i, '0'+str(n)+'b')] for i in nums]

# 平行移動しn次元超立方体へ
n_dim_hypercube = (np.array(nbit) - 0.5) * 2
n_dim_hypercube

>>> array([[-1., -1., -1., -1., -1.],
>>>        [-1., -1., -1., -1.,  1.],
>>>        [-1., -1., -1.,  1., -1.],
>>>        [-1., -1., -1.,  1.,  1.],
>>>        [-1., -1.,  1., -1., -1.],
>>>        [-1., -1.,  1., -1.,  1.],
>>>        [-1., -1.,  1.,  1., -1.],
>>>        [-1., -1.,  1.,  1.,  1.],
>>>        [-1.,  1., -1., -1., -1.],
>>>        [-1.,  1., -1., -1.,  1.],
>>>        [-1.,  1., -1.,  1., -1.],
>>>        [-1.,  1., -1.,  1.,  1.],
>>>        [-1.,  1.,  1., -1., -1.],
>>>        [-1.,  1.,  1., -1.,  1.],
>>>        [-1.,  1.,  1.,  1., -1.],
>>>        [-1.,  1.,  1.,  1.,  1.],
>>>        [ 1., -1., -1., -1., -1.],
>>>        [ 1., -1., -1., -1.,  1.],
>>>        [ 1., -1., -1.,  1., -1.],
>>>        [ 1., -1., -1.,  1.,  1.],
>>>        [ 1., -1.,  1., -1., -1.],
>>>        [ 1., -1.,  1., -1.,  1.],
>>>        [ 1., -1.,  1.,  1., -1.],
>>>        [ 1., -1.,  1.,  1.,  1.],
>>>        [ 1.,  1., -1., -1., -1.],
>>>        [ 1.,  1., -1., -1.,  1.],
>>>        [ 1.,  1., -1.,  1., -1.],
>>>        [ 1.,  1., -1.,  1.,  1.],
>>>        [ 1.,  1.,  1., -1., -1.],
>>>        [ 1.,  1.,  1., -1.,  1.],
>>>        [ 1.,  1.,  1.,  1., -1.],
>>>        [ 1.,  1.,  1.,  1.,  1.]])
```

# 最後に

立方体と10進数と2進数の対応関係を可視化しておきます。図が立方体の各頂点で色分けされています。各色に対応する10進数と2進数をラベルとして付与しています。

![対応関係.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/365961/c80eb419-a8e3-1527-a069-7f56d96c3850.png)

# まとめ

　超立方体なんて簡単でしたねぇ（なんだただの2進数か…）。ここで言いたいのは、Ｎ次元の超立方体を無理やり3次元空間で見ようとすると理解が難しくなるので、Ｎ次元配列として見てすっきりしましょうと言うことです。作成したＮ次元配列を何らかの法則に従って3次元空間に投影してもいいですが…私は混乱するだけなので嫌いです。面白ければいいねでも押しておいてください。