kivantium活動日記

プログラムを使っていろいろやります

2020年7月

出来事

修士のときの指導教員のご厚意に甘えて、テーマをもらって研究をしていました。といっても大学が閉鎖されている影響でミーティング等は全てオンライン化されていたので一度も研究室に行きませんでした。9月から博士課程が始まりますが、今年いっぱいは日本にいる予定なのでメリハリのある研究生活を確立する必要がありそうです。

研究を進めるために読むべき文献がかなり量子力学の予備知識を仮定していたので、最近はずっと量子力学を勉強しています。ここまでに読んだ本を振り返っておきます。いろんな人が「量子力学を一冊で理解するのは無理だからいろんな本を読むべき」と言っているので、一冊の本にあまり時間をかけず、分からないところは流し読みして早めに読み終えていく方針で勉強しています。

東大の前期教養の量子力学の教科書として使われているらしい「量子論の基礎」を学部3年くらいのときに読みました。量子力学の公理から入ってEPRパラドックスベルの不等式まで説明しています。量子力学古典力学とどう違うのかを理解する上ではこの本のような構成でいいのかもしれませんが、水素原子のシュレーディンガー方程式の解き方のような化学への応用にはほとんど触れられていなかったため、化学への応用を知りたい自分には不向きでした。

量子化学 上巻

量子化学 上巻

M1のときに研究室の人から教えてもらった原田量子化学を読みました。この本はシュレーディンガー方程式の導出から始まって現代の量子化学計算の基本までを丁寧に数式で説明していて、化学への応用を知るにはこれが良さそうでした。量子化学の本ではマッカーリ・サイモン物理化学も有名なのでざっと読んでみましたが、計算の詳細が全部演習問題に回されているのでこれではざっくりとしか分からないように思いました(ざっくり流れだけ知りたい人はこっちでもいいのかもしれません……)

実際に計算をするならザボを読む必要があると言われたのでM1〜M2にかけて読みました。上巻はかなり詳しくて、これだけ読めばハートリー・フォック法の実装ができるようになります。下巻はかなり飛ばし気味なので実装ができるほどではありませんでした。

ここからは今年読んだ本です。「量子化学」の本は化学寄りすぎる傾向があるので、もう少し物理寄りの「量子力学」を読もうと思って講談社基礎物理学シリーズのこれを読みました。清水本と違って波動力学を中心に話を進めているので1次元ポテンシャルや水素原子のシュレーディンガー方程式が実際に解けるようになります。

量子力学を学ぶための解析力学入門 増補第2版 (KS物理専門書)

量子力学を学ぶための解析力学入門 増補第2版 (KS物理専門書)

  • 作者:高橋 康
  • 発売日: 2000/10/24
  • メディア: 単行本(ソフトカバー)
量子力学では解析力学から得たアイデアが使われているため、解析力学も勉強しようと思いました。先月読んだ基礎物理学シリーズの解析力学の本で紹介されていたので読みました。量子力学に必要なところに絞って紹介されているので短いですが、古典力学への応用が全然出てこないのでこれだけ読んでも解析力学はよく分からないと思います。また、今のところはこの本で出てきた解析力学の知識が役に立った実感がありません。

量子力学〈1〉 (基礎物理学選書5A)

量子力学〈1〉 (基礎物理学選書5A)

量子力学〈2〉 (基礎物理学選書5B)

量子力学〈2〉 (基礎物理学選書5B)

古い本ですが、上の本で入門に良いと紹介されていたので知りました。講談社基礎物理学シリーズの量子力学の下巻は化学への応用からかなり離れていたので、多体系の話が書いてあるこちらを先に読みました1巻は今まで読んだ本とかなり被っていたのでざっと流し読みしました。2巻はハートリー・フォック近似や第二量子化が今までの本とは違う観点から書かれていて参考になりました。2巻の相対論的量子力学以降の話はあまり理解できていませんが、スピンが自然に出てくる話などは面白かったです。ただ、フォントや文体からやや古さを感じてちょっと親しみにくかったです。また、説明が簡潔で結論が弱く、だから何?となる話の流れも多かった気がします。同じシリーズに演習書があるので本気で勉強するならやったほうがいいのかもしれません……。

今は講談社量子力学の下巻を読んでいます。

読んだ本

芳文社のキャンペーンがあったのできららコミックスをたくさん買いました。

人間との恋愛を禁止されたアンドロイドの恋愛話です。「人間との恋愛を禁止」という要素がメインで、身分違いの恋を現代っぽくしたかったのかなと思います。SFっぽさはかなり薄かったのでロボット萌えを求める人には向きませんでした。

これを買った一因にはATRIをやってロボットとの恋愛ものブームが個人的に再燃したのがあります。今月はほかにもルーシィ ~彼女が望んでいたもの~をやりました。どちらもロボット三原則を前面に押し出していて、ロボット三原則大好きオタクとしては嬉しかったです。三原則の活用のうまさでは元ネタのわれはロボットに及ばないと感じてしまいましたが……(SFオタクは厄介)。ロボットとの恋愛ではとなりのロボットで出てくるような、機械特有の愛情表現の描写がかなり好きなのでそういう方向の作品がもっと増えてほしいです。

アニメが面白かった記憶があるので買いました。アニメをいい具合に忘れていたので面白かったです(選挙の詳細はアニメと違っていました)。絵が可愛いのは正義。

きららベースで連載を読んだときに面白かった記憶があったので読みました。生きるのがつらい要素を求めていたので結末には裏切られた思いがします。

石ミコの話が進んでよかったです。

かぐや様の人が原作なので買いました。1巻ではまだ序章が終わっただけなのでなんとも言えません。

東西南北! (百合姫コミックス)

東西南北! (百合姫コミックス)

なもり先生の新作なので買いました。24ページで200円という価格設定はかなり強気だと思いました。

資本主義の倒し方を求めて読みました。これを読んでも資本主義の倒し方は分かりませんでしたが、資本論に書いてあることはちょっと理解できた気がします。

RISC-Vクロスコンパイラで生成したバイナリを自作RISC-V上で実行する

4連休の課題としてFPGAで簡単なCPUを作っているので、その進捗を記録しておきます。

RISC-V (RV32I) の作成

とりあえず今回は確実に動くCPUを作ることを目標にしました。 パイプラインなどは実装せず、フェッチ→デコード→実行→メモリ・アクセス→書き戻しの5段階にそれぞれ1クロック使って、1命令に5クロックかける設計になっています。 命令セットにはRISC-Vの一番基本的な構成であるRV32Iを採用しましたが、簡単のため特権命令や割り込み周りは省略しました。 ハードウェアには以前使ったDigilentのBasys 3を使って、Vivadoで開発しました。 kivantium.hateblo.jp

あまり工夫したところはないので実装の詳細は説明しません。ソースコードはここに置いてあります。 github.com

RISC-Vクロスコンパイラのインストール

RISC-V向けのGCCriscv-gnu-toolchainというリポジトリで公開されています。以前はriscv-toolsというリポジトリ以下で公開されていたものが移動したらしいので、古い記事を参考にするときは気をつけてください。 github.com

READMEに従ってインストールするだけですが、configureで32bit用に設定する必要があります。ソースコードだけで10GB近くあるのでディスク容量に注意してください。

sudo apt-get install autoconf automake autotools-dev curl python3 libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison flex texinfo gperf libtool patchutils bc zlib1g-dev libexpat-dev
git clone --recursive https://github.com/riscv/riscv-gnu-toolchain
./configure --prefix=/opt/riscv32 --with-arch=rv32im --with-abi=ilp32d
make linux

/opt/riscv32以下にインストールされるのでPATHを通しておきます。

export PATH=/opt/riscv32/bin:$PATH

バイナリの作成

簡単な例として、フィボナッチ数列の第10番目の項を再帰で求めるプログラムを実行することにします。test.cを以下の通り作成します。main関数を抜けないようにするために最後に無限ループを実行しています。

int fib(int n) {
  if(n <= 1) return 1;
  return fib(n-1) + fib(n-2);
}

int main() {
  fib(10);
  for(;;) {}
  return 0;
}

これを自作CPUで動くようにコンパイルします。

普通にコンパイルしてしまうと未実装の命令を使った初期化ルーチンが走ってしまうので、まずはそれを無効にします。start.Sを以下の通り作成します。

.section .text.init;
.globl _start
_start:
    call main

何もせずにmainを呼び出すアセンブリになっています。

次に、命令を0番地から実行するように指定します。link.ldを以下の通り作成します。

OUTPUT_ARCH( "riscv" )
ENTRY(_start)

SECTIONS
{
  . = 0x00000000;
  .text.init : { *(.text.init) }
  .tohost : { *(.tohost) }
  .text : { *(.text) }
  .data : { *(.data) }
  .bss : { *(.bss) }
  _end = .;
}

以下のようにしてバイナリを生成します。

riscv32-unknown-elf-gcc -march=rv32i -c -o start.o start.S
riscv32-unknown-elf-gcc -march=rv32i -c -o test.o test.c
riscv32-unknown-elf-ld test.o start.o -lc -L/opt/riscv32/riscv32-unknown-elf/lib/ -Tlink.ld -nostartfiles -static -o test.elf
riscv32-unknown-elf-objcopy -O binary test.elf test.bin
hexdump -v -e '1/4 "%08x" "\n"' test.bin > test.hex

最後に出来上がるtest.hexには、CPUが実行する命令列が16進数で書かれています。

074000ef
fe010113
00112e23
(中略)
00a00513
f7dff0ef
0000006f

ELF形式のファイルに対してobjdumpを実行すると逆アセンブルした結果を見ることができます。

$ riscv32-unknown-elf-objdump -d test.elf

test.elf:     ファイル形式 elf32-littleriscv


セクション .text.init の逆アセンブル:

00000000 <_start>:
   0:   074000ef            jal ra,74 <main>

セクション .text の逆アセンブル:

00000004 <fib>:
   4:   fe010113            addi    sp,sp,-32
   8:   00112e23            sw  ra,28(sp)
   c:   00812c23            sw  s0,24(sp)
  10:   00912a23            sw  s1,20(sp)
  14:   02010413            addi    s0,sp,32
  18:   fea42623            sw  a0,-20(s0)
  1c:   fec42703            lw  a4,-20(s0)
  20:   00100793            li  a5,1
  24:   00e7c663            blt a5,a4,30 <fib+0x2c>
  28:   00100793            li  a5,1
  2c:   0300006f            j   5c <fib+0x58>
  30:   fec42783            lw  a5,-20(s0)
  34:   fff78793            addi    a5,a5,-1
  38:   00078513            mv  a0,a5
  3c:   fc9ff0ef            jal ra,4 <fib>
  40:   00050493            mv  s1,a0
  44:   fec42783            lw  a5,-20(s0)
  48:   ffe78793            addi    a5,a5,-2
  4c:   00078513            mv  a0,a5
  50:   fb5ff0ef            jal ra,4 <fib>
  54:   00050793            mv  a5,a0
  58:   00f487b3            add a5,s1,a5
  5c:   00078513            mv  a0,a5
  60:   01c12083            lw  ra,28(sp)
  64:   01812403            lw  s0,24(sp)
  68:   01412483            lw  s1,20(sp)
  6c:   02010113            addi    sp,sp,32
  70:   00008067            ret

00000074 <main>:
  74:   ff010113            addi    sp,sp,-16
  78:   00112623            sw  ra,12(sp)
  7c:   00812423            sw  s0,8(sp)
  80:   01010413            addi    s0,sp,16
  84:   00b00513            li  a0,11
  88:   f7dff0ef            jal ra,4 <fib>
  8c:   0000006f            j   8c <main+0x18>

C言語で書いた通り、再帰でフィボナッチ数を求めた後、`8c'を無限ループするプログラムになっていることが確認できます。

生成した16進数の命令列はSystemVerilogの$readmemhを利用して命令メモリに埋め込んでいます(ソースコード)。コードを変更するたびに論理合成をやり直す必要がありますが、命令列を外部から読み込ませるのは面倒なのでこうしました。

実機での動作

関数の引数と戻り値が入るa0レジスタの値を10進数で7セグLEDに表示する回路を組みました。しばらく計算した後、最終的な関数の返り値である89が表示されます。(動作を見やすくするためにクロックを10万分周しています)

今後の課題

xv6が去年からRISC-Vに対応したそうなので、xv6が動くようなCPUが作れると良いです。 OSを動かすためには割り込みなどの特権命令やスーパーバイザーモードでの仮想アドレスを実装しないといけないみたいので道のりは険しそうですが……

参考文献

ツールチェインの使い方が特に参考になりました。

github.com 上のFPGAマガジンで実装されているRISC-Vです(本誌からリンクされてなかった……)

1命令を5クロックで実行する設計はここから持ってきました(このコードではデコーダーやALUが順序回路になっていますが、自分の実装では組み合わせ回路になっています。2クロック無駄になっていますが、簡単のためです……)

mindchasers.com ツールチェインのコンパイルオプションはここを参考にしました。

2020年6月

出来事

インターンの話

先月から休職に入っていましたが、復帰しないまま退職しました。

自分は大学でしか生きられないと悟ったので、7月からは修士の研究室に戻って研究します。

研究の話

量子力学有機化学情報科学の三正面作戦を取る必要があるので、ニート期間を活かして勉強していました。どっちつかずの状態になってしまうことはないでしょうか?

休職中にやっていたテーマがうまく行きつつあるので今年中に次回作が出せればいいなと思っています。

創薬に役に立っ」と言えるようになるまで頑張ります。

読んだ本

量子力学の本を読んでいるとハミルトニアンポアソン括弧などの解析力学の概念が援用されていることがあります。解析力学を知らなくても一応読めるのですが、全く知識がないのは気持ち悪いので勉強しました。

講談社基礎物理学シリーズは高校生のときに読むよう薦められたのですが、当時はお金がなくて図書館で借りてちらっと読むくらいのことしかできませんでした。このシリーズは学部前半で読むことが想定されている気がしますが、簡単なところからスタートする方が結局は近道になると思うので謙虚に読んでいこうと思います。

怠ける権利 (平凡社ライブラリー)

怠ける権利 (平凡社ライブラリー)

自然の本能に復し、ブルジョワ革命の屁理屈屋が捏ねあげた、肺病やみの人間の権利などより何千倍も高貴で神聖な、怠ける権利を宣言しなければならぬ。一日三時間しか働かず、残りの昼夜は旨いものを食べ、怠けて暮らすように努めなければならない。

フランスの二月革命で掲げられた労働の権利に対する反駁として1880年に書かれたもので、当時の政治家ブラックによると「マルクスエンゲルス共産党宣言を除いて、社会主義関係の著書では、これほどまで多様の言語に翻訳されたものはない」とされている文章です。(それほど人口に膾炙したにも関わらず、マルクスエンゲルスからは無視されたことをはじめ、後世の研究者からもあまり注目されていないようです……)

確かに「労働の権利」を当たり前だと思わずに「怠ける権利」を主張していくべきでした。今までの自分の態度を深く反省しています。

継続高校の人々が各高校を回って食事するグルメ漫画。2巻で完結でした。

自動微分ライブラリJAXを用いた対称行列の固有値の微分

JAXという自動微分ライブラリが流行りそうな機運があるので遊んでみます。

github.com

インストール

READMEに書いてある通りにやりました。

pip install --upgrade pip
pip install --upgrade jax jaxlib 

基本的な使い方

The Autodiff Cookbook — JAX documentation を読んで下さい

固有値の最小化の例

ここまでとても雑な説明だったのは、固有値の自動微分が今回の記事のメインテーマだからです。固有値の勾配を求める需要なんてないだろうと思っていたのですが、シュレディンガー方程式固有値方程式なので、エネルギーの最小化をするためには固有値を最小化する必要があり、そのために固有値の勾配が欲しいという需要があるようです。(この論文では実際に自動微分を使ってエネルギーの最小化を行っています)

JAXでは一般の固有値の勾配はまだ実装されていないようなので、対称行列の固有値の勾配を使った例題を解くことにします。

\displaystyle{
A=\left(
    \begin{array}{cc}
      1 &x \\
      x & -1
    \end{array}
  \right)
}

として、行列Aの最大固有値が最小になるようにしてみます。(ここでは実数の範囲のみを考えることにします)固有方程式を解くと、固有値λは

\displaystyle{
\lambda = \pm\sqrt{x^2+1}
}

となるので、最大固有値の最小値はx=0のときに1.0になります

xを与えたときにAの最大固有値を返す関数をfunc最急降下法で最小化するプログラムは以下のようになります。

import jax.numpy as np
from jax.ops import index, index_update
from jax import grad, jit

@jit
def func(x):
    A = np.zeros((2, 2))
    A = index_update(A, index[0, 0], 1)
    A = index_update(A, index[0, 1], x)
    A = index_update(A, index[1, 0], x)
    A = index_update(A, index[1, 1], -1)
    w, v = np.linalg.eigh(A)
    return np.max(w).real

func_grad = jit(grad(func))
alpha = 0.1
x = 1.0
for _ in range(100):
    print("x={}, f(x)={}".format(x, func(x)))
    x -= alpha * func_grad(x)

print("min value: {} (x={})".format(func(x), x))

コメント

  • 関数に@jitをつけるとJITコンパイルが行われて実行が高速になります。jit(grad(func))のようにすると、勾配関数もJITコンパイルできます。
  • JAXで配列に添字でアクセスするとエラーになるので、代わりにindex_updateを使っています。詳細は🔪 JAX - The Sharp Bits 🔪 — JAX documentationを見てください。(さすがにこれはあまりに汚いのでもう少しいいやり方があるかもしれません)
  • 固有値を求めるためにjax.scipy.linalg.eighを使っています。この関数は本来エルミート行列用なので、固有値複素数で返ってきます。ここでは問題を実数の範囲に限定しているので実部を取っています。

実行結果は次のようになりました。

x=1.0, f(x)=1.4142135381698608
x=0.9292893409729004, f(x)=1.3651295900344849
x=0.8612160086631775, f(x)=1.3197321891784668
x=0.7959591150283813, f(x)=1.2781044244766235
x=0.7336825728416443, f(x)=1.2402782440185547
(中略)
x=6.46666157990694e-05, f(x)=1.0
x=5.819995567435399e-05, f(x)=1.0
x=5.237996083451435e-05, f(x)=1.0
x=4.714196620625444e-05, f(x)=1.0
x=4.242776776663959e-05, f(x)=1.0
x=3.818498953478411e-05, f(x)=1.0
min value: 1.0 (x=3.43664905813057e-05)

最急降下法がそれっぽく動いていることが確認できました。この程度の問題では自動微分を使うまでもないですが、問題がもう少し複雑になるとJITによる高速な自動微分のメリットを享受できます。 今回は最急降下法を使いましたが、scipyと組み合わせればBFGSなどのもう少し高度な最適化手法を使うことができます(参考: A brief introduction to JAX and Laplace’s method - anguswilliams91.github.io

固有値微分とは一体なんなのかという話もする必要があるのですが、まだよく理解できていないので今日はとりあえずここまでにして後日更新します。

メモ欄

added jvp rule for eigh, tests by levskaya · Pull Request #358 · google/jax · GitHub

2020年5月

出来事

インターンの話

4月の記事に書いたようにインターンの内容に不満があったので、ゴールデンウィーク明けに今の業務をつづける意味を感じないという話を上司にしたところ、とりあえず出社が可能になるまでは休職することになりました。その時は休職は明日からでも良いという話でしたが、事務手続きに2週間ほどかかったので実際に休職になったのは先週からです。休職した分インターン期間を変更するなどの配慮をしてもらいましたが、今後どうなるかは状況次第です。 今までの経験を振り返っても、何も分からない担当者と働いてひどい目にあったときはともかくとして、特に業務内容や人間関係に問題がないときでも逃げるように辞めていたような気がしますし、上手に労働ができた記憶がありません。これからどうやって生きていけばいいんだろうなぁ。

Webサービスの話

業務後の時間を使ってひたすらWebサービスを作っていました。

nijisearch.kivantium.net

自分からイラストを探しにいかなくても勝手にイラストが集まってくるのは見ていて楽しいので作った意味があったと思っています。 サーバー代くらいは稼げるといいなと思ってGoogle Adsenseの広告を貼っていますが、今のPVでは全く利益が出ないので世の中の厳しさを感じています。自分が欲しいものを作っただけなので別に儲からなくてもいいのですが。 このサービスの本質はタイムラインからのイラスト自動収集だけで残りはだいたいやるだけなのですが、作業の量としては圧倒的にやるだけ部分が多くなっています。世の中ってそういうものよね。勉強しないといけないことがたくさんあるので、あまりこれに時間を使ってはいけないと思っているのですが、手を動かした分だけ機能が増えるのは楽しいのでつい弄ってしまってよくないです。

読んだ本

業務時間と趣味の開発で時間を取られて他のことをする時間がだいぶ減っていました。

統計力学〈1〉 (新物理学シリーズ)

統計力学〈1〉 (新物理学シリーズ)

統計力学を使ってニューラルネットワークを解析するみたいな話を聞いて解析力学を勉強しようと思って学部生のときに買ったものの積んでいた本です。等重率の原理から出発して熱力学のいろんな現象が出てくる話が中心だったのですが、熱力学にあまり思い入れがないこともあってか前評判ほどの面白さは感じられませんでした。

専門ではないが知っておきたい分野の本をどのくらいしっかり読み込めばいいのか分からないでいます。というか、勉強の仕方が未だに分かりません。ここ数年は本がなくても内容が復元できるようなノートを書きながら本を読み進めて、一冊分ノートを書き終わったらおしまいという勉強をしているのですが、時間の割にあまり身についていない気がします。ただ読むだけだと身につかないですが、だからといって何も見ずに内容を再現できるレベルで理解するようにしていたらいつになっても読み終わらないわけで、その中間を模索しています。

山田エルフ大先生がかわいかったです。

なもり先生の絵がかわいかったです。

知能がない。

Django ChannelsでWebsocket通信を行ってVue.jsで表示する

前回の続きです。 kivantium.hateblo.jp

サーバー上で重たい処理をする場合、処理が全て終わってからHTMLを生成しているとかなりレスポンスが悪くなってしまいます。先に結果表示ページのHTMLを表示しておいて、中身を後からWebsocketで順次通信するといい感じになりそうです。Websocket通信したデータを表示するためにVue.jsを使うことにします。

前回からの変更点

前回のHello, worldで使ったディレクトリ構成を編集する形で作業を進めます。

mysite/routing.py を以下の内容で書き換えます。ws/test/にアクセスすることでWebsocket通信を行えるように設定しています。

from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from django.urls import path

from . import consumers

websocket_urlpatterns = [
    path('ws/test/', consumers.Consumer),
]

application = ProtocolTypeRouter({
    # (http->django views is added by default)
    'websocket': AuthMiddlewareStack(
        URLRouter(
            websocket_urlpatterns
        )
    ),
})

mysite/consumers.py にWebsocket通信で行う処理を記述します。本当は非同期処理をするべきらしいのですが、動く書き方が分からなかったので同期処理で書いています。(参考: django で Websocket - 空のブログ

import json
import time
import threading

from channels.generic.websocket import WebsocketConsumer

class Consumer(WebsocketConsumer):
    # 接続されたときの処理
    def connect(self):
        # 接続を許可する
        self.accept()
        # メッセージ送信関数を新しいスレッドで呼び出す
        # 本当は非同期で書くべきだがうまく動かなかった
        self.sending = True
        self.sender = threading.Thread(
            target=self.send_message, args=('Hello', ))
        self.sender.start()

    # 接続が切断されたときの処理
    def disconnect(self, close_code):
        # スレッドを終了するフラグを立てる
        self.sending = False
        # スレッドの終了を待つ
        self.sender.join()

    # メッセージを受け取ったときの処理
    def receive(self, text_data):
        text_data_json = json.loads(text_data)
        message = text_data_json['message']
        print("Received message:", message)

    # メッセージ送信関数
    def send_message(self, message):
        while True:
            # 終了フラグが立っていたら終了する
            if not self.sending:
                break
            self.send(text_data=json.dumps({
                'message': message,
            }))
            time.sleep(1)

mysite/templates/mysite/index.html にこれと通信するためのHTMLとJavaScriptを書きます。

<!doctype html>
<html>
  <head>
    <title>test</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <ul >
        <li v-for="message in messages">[[ message ]]</li>
      </ul>
    </div>
    <script>
      var vm = new Vue({
        el: '#app',
        // Vueの記号がDjangoと被らないようにする
        delimiters: ['[[', ']]'],
        data: {
          messages: [],
          ws: new WebSocket(
            // http or https の判定
            // https://www.koatech.info/blog/vue-websocket-sample/
            (window.location.protocol == "https:" ? "wss" : "ws")
            + '://' + window.location.host + '/ws/test/'
          ),
        },
        created: function() {
          // Vueオブジェクトにアクセスするために変数化する
          // https://www.koatech.info/blog/vue-websocket-sample/
          const self = this;

          // メッセージを受け取ったときの処理
          self.ws.onmessage = function(e) {
            const data = JSON.parse(e.data);
            console.log(data.message)
            console.log(vm.messages)
            // メッセージを表示する
            vm.messages.push(data.message);
            // サーバーにメッセージを送る
            self.ws.send(JSON.stringify({
              'message': "Konnichiwa",
            }));
          };

          // 接続が切断されたときの処理
          self.ws.onclose = function(e) {
            console.error('Websocket has been closed unexpectedly');
          };
        }
      })
    </script>
  </body>
</html>

mysite/views.py を次のように書き換えてindex.htmlを表示するようにします。

from django.shortcuts import render

def index(request):
    return render(request, 'mysite/index.html')

最後に、テンプレートを認識させるために mysite/settings.pyINSTALLED_APPSmysite を追加します。

INSTALLED_APPS = [
    ......
    'channels',
    'mysite',
]

以上の変更を行ったものをデプロイしてブラウザで閲覧すると1秒ごとにHelloが追加される様子が確認できます。

f:id:kivantium:20200502150709g:plain
スクリーンショット

自分の目的にはこれで十分でしたが、チャットなどのより高度なことをしたい場合はChennelsのドキュメントを読んで下さい。 channels.readthedocs.io

Django ChennelsアプリをNginxとSupervisorでデプロイする

DjangoでWebsocketを使うときにはChannelsというライブラリがよく使われています。これまではHerokuにデプロイをしてきましたが、HerokuとChannelsの相性が良くないのかすぐに接続が切れてしまうので、これからはAWS上で開発しようと思いました。公式ドキュメントを読んでもデプロイ方法がよく分からなかったのでメモしておきます。

AWS LightsailでUbuntu 18.04のインスタンスを立てたとして、SSHで入ってからHello, world!するところまでを見ていきます。

ライブラリのインストール

Daphneの起動を楽にするためにvenvを使います。(参考: Djangoのインストール · Django Girls Tutorial

ssh ubuntu@xxx.xxx.xxx.xxx
sudo apt update
sudo apt install python3-venv
python3 -m venv env
source env/bin/activate

requirements.txtに以下を記述します。

django~=3.0.5
channels~=2.4.0

pipをアップデートしてから必要なライブラリをインストールします。

python -m pip install --upgrade pip
pip install -r requirements.txt

Hello, world!アプリの設定

Django プロジェクトを作成します。

django-admin startproject mysite
cd mysite

基本的にはChannels公式ドキュメントのInstallationに従って設定します。簡単のために本番環境と開発環境の設定の分離などは無視します。

mysite/settings.py を以下のように編集します。diffを示しています。

-ALLOWED_HOSTS = []
+ALLOWED_HOSTS = ['*']  # 本当は適切なホストを指定するべきだが簡単のため全て許可
 
(略)

INSTALLED_APPS = [
     'django.contrib.sessions',
     'django.contrib.messages',
     'django.contrib.staticfiles',
+    'channels',
 ]
 
+ASGI_APPLICATION = "mysite.routing.application"
+
 MIDDLEWARE = [
     'django.middleware.security.SecurityMiddleware',
     'django.contrib.sessions.middleware.SessionMiddleware',

mysite/routing.py を以下の内容で作成します。

from channels.routing import ProtocolTypeRouter

application = ProtocolTypeRouter({
    # Empty for now (http->django views is added by default)
})

mysite/urls.py を以下の内容で作成します。

from django.urls import path

from . import views

urlpatterns = [
    path('', views.index, name='index'),
]

mysite/views.py を以下の内容で作成します。

from django.http import HttpResponse


def index(request):
    return HttpResponse("Hello, world!")

ここまで来たら python manage.py runserver を実行してエラーが出ないことだけ確認します。(サーバー上にあるのでこの時点ではブラウザで表示確認ができません)

NginxとSupervisorの設定

ここもChannels公式ドキュメントのDeployingに従って設定するだけなのですが、この通りにやっても動かなかったので以下のStackOverflowに従ってアレンジしました。

stackoverflow.com

まずはNginxとSupervisorをインストールします。

sudo apt install nginx supervisor

mysite/asgi.pyを以下の内容で作成します。

"""
ASGI entrypoint. Configures Django and then runs the application
defined in the ASGI_APPLICATION setting.
"""

import os
import django
from channels.routing import get_default_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")
django.setup()
application = get_default_application()

/etc/supervisor/conf.d/asgi.confを以下の内容で作成します。

[fcgi-program:asgi]
# TCP socket used by Nginx backend upstream
socket=tcp://localhost:8000

# Directory where your site's project files are located
directory=/home/ubuntu/mysite

# Each process needs to have a separate socket file, so we use process_num
# Make sure to update "mysite.asgi" to match your project name
command=/home/ubuntu/env/bin/daphne --fd 0 --access-log - --proxy-headers mysite.asgi:application
# Number of processes to startup, roughly the number of CPUs you have
numprocs=4

# Give each process a unique name so they can be told apart
process_name=asgi%(process_num)d

# Automatically start and recover processes
autostart=true
autorestart=true

# Choose where you want your log to go
stdout_logfile=/var/log/asgi.log
redirect_stderr=true

設定を読み込みます。

sudo supervisorctl reread
sudo supervisorctl update

/etc/nginx/sites-available/defaultを以下のように編集します。

upstream channels-backend {
    server localhost:8000;
}
...
server {
    ...
    location / {
        try_files $uri @proxy_to_app;
    }
    ...
    location @proxy_to_app {
        proxy_pass http://channels-backend;

        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        proxy_redirect off;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Host $server_name;
    }
    ...
}

設定を読み込みます。

sudo service nginx reload

ブラウザのアドレス欄にこのサーバーのIPアドレスを入力すれば、Hello, world! と出力されたページを確認することができます。 次回はこのサーバーを使って簡単なWebsocketを使ったプログラムを書きます。

広告コーナー