Feynman Diagram が TeX Live 2019/2020 でちょん切れないようにする

TeX Live 2018 + Mojave の環境までは LaTeXiT で Feynman diagram を描くときは阪大の山中さんのページを参考にしていましたが、Catalina + TeX Live 2019 にしたら図がちょん切れるようになった(bounding box がおかしくなった)ので、解決する方法の覚え書きです。

osksn2.hep.sci.osaka-u.ac.jp

詳細は StackExchange にも質問で書きました。

tex.stackexchange.com

新しいやり方

  • latex + dvipdf は使わない(そもそも、dvipdf だと図が真っ白になる)
  • dvipdfm にすると図がちょん切れるので、これも使わない
  • LaTeXiT の preamble に \usepackage{feynmp} だけでなく \DeclareGraphicsRule{*}{mps}{*}{} も追加する
  • mpost の実行後、pdflatex で PDF を出力させる

f:id:oxon:20200712130650p:plain

f:id:oxon:20200712130810p:plain

古いやり方だと \usepackage{feynmp} を追加するだけでしたが、これだけだと mpost コマンドの生成する拡張子なしの PS ファイルを pdflatex が正しく取り扱えません。そのため、古いやり方では latexdvipdf を使って DVI を経由させていたのだと思います。

\DeclareGraphicsRule{*}{mps}{*}{} も追加することで、拡張子のないファイルを pdflatex が扱えるようになるので、TeX Live 2019 や 2020 で現れる dvipdf もしくは dvipdfm の問題(仕様?)を回避することができます。

古いやり方

TeX Live 2018 まで問題なかった山中さんのやり方で dvipdfdvipdfm にすると絵は出てきますが、下がちょん切れていますね。

f:id:oxon:20200712130930p:plain

f:id:oxon:20200712131020p:plain

brew/pip3 install ipython の違い

Homebrew を使って IPython を導入する際、brew install を使うやり方と、pip3 install をやり方の主に 2 つの方法があると思います。前者でやると IPython 起動時に PYTHONPATH を勝手に書き換えてしまうことが分かり、少しはまりました。そもそも ipython コマンドというのは bash script だったり、Python script だったり、環境によって違うのだということを知りました。

macOS 10.15.5 です。

Homebrew 環境の構築

これは書くまでもありませんが、https://brew.sh/の説明通りに次のコマンドを実行します。

$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"

brew install ipython の場合(非推奨)

さて、IPython を入れたいのだから brew install ipython と単純にやると次のようになります。

$ brew install ipython
$ rehash
$ which python
/usr/bin/python
$ which python2
/usr/bin/python2
$ which python3
/usr/bin/python3
$ which ipython
/usr/local/bin/ipython
$ which pip 
pip not found
$ which pip3
/usr/bin/pip3

つまり、macOS 標準の Python をデフォルトとして維持したまま ipython コマンドだけ入ります(実際には他にも色々と入りますが)。pip3 も入りません。

そして ipython の中身は次の bash script です。PYTHONPATH を勝手に書き換えてしまいます。

$ cat `which ipython`
#!/bin/bash
PYTHONPATH="/usr/local/Cellar/ipython/7.15.0/libexec/lib/python3.8/site-packages:/usr/local/Cellar/ipython/7.15.0/libexec/vendor/lib/python3.8/site-packages" exec "/usr/local/Cellar/ipython/7.15.0/libexec/bin/ipython" "$@"

これで PyROOT を使おうと思っても PYTHONPATH から ROOTSYS/lib が消えてしまい、おかしいおかしいと悩んでしまいました。これは bug じゃないかと思いますが、Homebrew の issue report をどこに上げるのかよく分からなかったので、放置しています。

brew install python を先にする場合(非推奨)

$ brew install python
$ rehash 
$ which python 
/usr/bin/python
$ which python2
/usr/bin/python2
$ which python3
/usr/local/bin/python3
$ which pip 
pip not found
$ which pip3
/usr/local/bin/pip3

python3 コマンドが /usr/local/bin に入り、pythonpython2 は OS 標準です。この場合だと、pip3 も入りました。

$ brew install ipython
$ rehash
$ which ipython
/usr/local/bin/ipython

ipython の中身は同じです。

$ cat `which ipython`
#!/bin/bash
PYTHONPATH="/usr/local/Cellar/ipython/7.15.0/libexec/lib/python3.8/site-packages:/usr/local/Cellar/ipython/7.15.0/libexec/vendor/lib/python3.8/site-packages" exec "/usr/local/Cellar/ipython/7.15.0/libexec/bin/ipython" "$@"

pip3 install の場合(推奨)

$ brew install python
$ rehash
$ pip3 install ipython
$ rehash

brew install python の後に rehash を忘れないようにしましょう。そうしないと、/usr/bin/pip3 が使われてしまいます。

cat `which ipython`
#!/usr/local/opt/python/bin/python3.7
# -*- coding: utf-8 -*-
import re
import sys
from IPython import start_ipython
if __name__ == '__main__':
    sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
    sys.exit(start_ipython())
$ cat `which ipython3`
#!/usr/local/opt/python/bin/python3.7
# -*- coding: utf-8 -*-
import re
import sys
from IPython import start_ipython
if __name__ == '__main__':
    sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
    sys.exit(start_ipython())

はい、こっちだと PYTHONPATH を勝手に書き換えませんし、bash ではなく Python です。こちらのやり方が推奨です。

Conda の場合

これは conda create で新しい環境を作ったときに入った ipython コマンドの中身です。また中身が違いますが、ほとんど一緒です。

#!/Users/oxon/anaconda3/envs/sstcam_root/bin/python

# -*- coding: utf-8 -*-
import re
import sys

from IPython import start_ipython

if __name__ == '__main__':
    sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
    sys.exit(start_ipython())

macOS Catalina で PyVISA/PySerial を使う、に加えて初心者向けの丁寧な解説

macOS Catalina で PyVISA と PySerial を使うときの覚え書きです。PyVISA のほうは普通にやると、はまります。詳しい人向けの結論を先に書くと、NI-GPIB を先に入れないと NI-VISA が入らない、です。

環境

  • macOS Catalina 10.15.5
  • Python 3.7.6(brew install python で)
  • PyVISA 1.10.1(pip3 install pyvisa で)
  • PySerial 3.4(pip3 install pyserial で)
  • NI-GPIB 19.5
  • NI-VISA Runtime 19.0.0

経緯

以前にこういう記事を書いたのですが、かなり情報が古くなってしまいました。当時は 32 bit と 64 bit が混在している時代で、Python も 32 bit だったり、National Instruments も Mac 用のドライバの配布が遅かったりと、苦労していました。 oxon.hatenablog.com Mojave から Catalina 環境に移行するにあたり、少し PyVISA ではまったので、記録に残しておきます。

うちの学生向けに書くので、比較的丁寧です。

VISA とは

VISA(Virtual Instrument Software Architecture) というのは、いろいろな実験室の計測装置を共通のソフトウェアで簡単に使えるようにしましょうという規格です。「査証」やクレジットカードの VISA とは関係がありません。

en.wikipedia.org

実験室にある計測装置の背面を見てみると、電源ケーブル以外に色々なケーブルを接続できることに気がつきます。古い機種だと RS-232C と呼ばれる規格の端子がついています(シリアルポートとも呼ばれます)。これは 9 本の剥き出しのピン(オスの場合)だったり、9 本のピンが差し込めるようになっているメスのポートだったりします。昔は計算機側にもこのポートが 2 つずつ付いていることが多かったのですが、今どきは付いていません。しかし実験室では、特にデータ転送速度を重視しない機器(低圧電源や回転ステージなど)ではいまだに使われることが多いです。

ja.wikipedia.org

少し時代が経ち、オシロスコープのようにデータ量の多い装置では GPIB(IEEE の規格は IEEE 488)というケーブルの規格が使われるようになりました。最近はあまり見かけませんし、元々 GPIB 搭載の可能な機種でもオプション扱いのものが多かったと思うので、20〜30 年前くらいの高いデジタルオシロスコープなんかに付いていたりします(安いものにもついていたと思いますが、30 年前の安いものは既に廃棄処分されていると思います)。見た目がゴツくて格好いいやつです。写真はリンク先を参照。

ja.wikipedia.org

最近だと、USB 端子がつく機種も増えました。むしろ新しい機種で USB すら付いていない製品やそんなものを販売する会社は、手を出さないほうがいいんじゃないかという気がします。USB 1.1(最大 12 Mbps)は低速の用途にも良いですし、USB 2.0(最大 480 Mbps)ならオシロスコープのようなデータ量の多い装置にも十分耐えられます。USB 3.0(最大 5 Gbps)以降を使用している機種は、2020 年現在、まだ多くないと思います。

ja.wikipedia.org

オシロスコープのように通信量が大きい装置は、USB ではなく Ethernet ポートを備えたものもあります。機種によって 100 Mbps だったり 1 Gbps だったり 10 Gbps だったり、様々です。自分が実験で使う場合は USB 2 より Ethernet のほうが転送速度が早いため Ethernet を使いますが、この記事で説明するにはネットワークの設定の説明が面倒なのと、実験室ごとに設定がいろいろ変わるので、Ethernet の説明は省き USB 中心で説明します。

ja.wikipedia.org

さて、VISA の話に戻ると、こういう異なるケーブルや通信プロトコルごとにソフトウェアを書き換えるのは面倒なので、一括で同じように扱えるようにしようというのが、VISA です。例えば何か計算機からコマンドを周辺機器に送るとき、もしくは周辺機器から計算機にデータが送られてくるとき、VISA 経由でやれば、どのケーブルやプロトコルを使用しているのかはほとんど気にしなくて良くなります。

PyVISA とは

PyVISA は、Python から VISA に対応した周辺機器とやり取りするための機能を提供してくれます。macOS Catalina に Homebrew で Python 3 を導入している場合、次のように pip3 コマンドで PyVISA をインストールします。

$ pip3 install pyvisa

後述する NI-GPIB や NI-VISA のインストールされた環境では、この PyVISA を使うことによって周辺機器と簡単に通信することができるようになります。次の例は、IPython から Tektronix の MSO4102B というオシロスコープに接続してシリアルナンバーなどの情報を得ているところです。

$ ipython
In [1]: import visa                                                                     
In [2]: rm = visa.ResourceManager()                                                     
In [3]: rm.list_resources()                                                             
Out[3]: ('USB0::0x0699::0x041B::C022756::INSTR',)
In [4]: usb = rm.open_resource(rm.list_resources()[0])                                  
In [5]: usb.query('*IDN?')                                                              
Out[5]: 'TEKTRONIX,MSO4102B-L,C022756,CF:91.1CT FV:v2.90 \n'

RS-232C での通信の場合は、ボーレート(baud rate)やデリミター(delimiter)などを気に掛ける必要がありましたが、VISA なら勝手にうまいことやってくれます。

デバイスドライバの部分は、PyVISA 自身はやっていません。macOS の場合、PyVISA は National Instruments(NI)の NI-VISA というデバイスドライバーをバックエンドとして使用しています。

PyVISA を macOS Catalina に導入する手順

1. NI-GPIB(NI-488.2)を入れる

PyVISA を使うには NI-VISA が必要で、NI-VISA を Catalina に入れるには NI-GPIB を先に入れる必要があります。リンク先から、Supported OS に MacOS、Version に 19.5 を選択してディスクイメージ(拡張子 .dmg)をダウンロードします。

www.ni.com

NI-488.2 19.5.dmg を開くと NI-488.2 19.5.pkg が現れるので、これを開き、あとは指示に従ってインストールします。これは GPIB を Mac で使用するための NI 社製のデバイスドライバーです。再起動が必要です。

macOS 10.13 と 10.14 しか対応していないとなっていますが、10.15 で動作します。

もしも .pkg を開けないとダイアログが出てきた場合、右クリックから開くようにすると次のダイアログが出るので、Open を選択してください。

f:id:oxon:20200609155806p:plain

2. NI-VISA Runtime を入れる

次に、NI-VISA を入れます。同様に Mac 版の 19.0 を入れます。この際、Included Editions は Runtime にすれば十分です。Full でも良いですが、PyVISA だけの使用であれば不要です。 www.ni.com

NI-VISA_Runtime_19.0.0.dmg を開くと NI-VISA_Runtime_19.0.0.pkg が現れるので、これも同様にインストールしてください。再起動は多分必要ありません。

3. PyVISA を入れる

Homebrew 環境の場合、pip から入れてください。Python 3 であれば pip3 です。

$ pip3 install pyvisa  

4. インストールの確認

PyVISA とともに pyvisa-info というコマンドがインストールされるので、これが /Library/Frameworks/visa.framework/visa にある NI-VISA を自動的に検出できていれば成功です。

$ pyvisa-info      

Machine Details:
   Platform ID:    Darwin-19.5.0-x86_64-i386-64bit
   Processor:      i386

Python:
   Implementation: CPython
   Executable:     /usr/local/opt/python/bin/python3.7
   Version:        3.7.6
   Compiler:       Clang 11.0.0 (clang-1100.0.33.16)
   Bits:           64bit
   Build:          Dec 30 2019 19:38:26 (#default)
   Unicode:        UCS4

PyVISA Version: 1.10.1

Backends:
   ni:
      Version: 1.10.1 (bundled with PyVISA)
      #1: /Library/Frameworks/visa.framework/visa:
         found by: auto
         bitness: 64
         Vendor: National Instruments
         Impl. Version: 19922944
         Spec. Version: 5244928

もし abort したというエラーが出る場合、System Preferences の Security & Privacy で NI 製のソフトウェアがブロックされていないか確認してください。もしこの画像のようにブロックされている場合、鍵を外して Allow を押して、許可してやりましょう。 f:id:oxon:20200616145248p:plain

5. 動作確認

先述したコマンド例の繰り返しになりますが、VISA に対応しているはずの適当な実験装置を USB ケーブルで Mac に接続し、次のように真似して *IDN? コマンドを送信してみましょう。普通の装置は後述の SCPI というコマンド体系に準拠しているはずで、ほとんどの装置は *IDN? コマンドに対して機種情報を返します。

$ ipython
In [1]: import visa                                                                     
In [2]: rm = visa.ResourceManager()                                                     
In [3]: rm.list_resources()                                                             
Out[3]: ('USB0::0x0699::0x041B::C022756::INSTR',)
In [4]: usb = rm.open_resource(rm.list_resources()[0])                                  
In [5]: usb.query('*IDN?')                                                              
Out[5]: 'TEKTRONIX,MSO4102B-L,C022756,CF:91.1CT FV:v2.90 \n'

これで、Tektronix の MSO4102B-L という機種でシリアルナンバー(S/N)C022756 のものが繋がっているというのが分かります。ファームウェアのバージョンがおそらく 2.90 なのだと思います。

SCPI

さて、周辺機器の接続情報だけ得ても面白くないので、*IDN? 以外の SCPI について調べましょう。Wikipedia の記事にもあるように、SCPI は実験室で使うような測定装置とやり取りするための標準的なコマンドの文法の規格です。メーカーが異なっても、例えば Tektronix でも Keithley でも岩通でも、SCPI で制御できる装置は似たようなコマンドで動作させることができます。

en.wikipedia.org

例えば上述の Tektronix のオシロスコープの電圧表示範囲を変更してみましょう。文法を調べるには、例えば「MSO4102 programmer's manual」などで Google 検索すると PDF が出てくるはずです。次のページが出てきたので、PDF をここから入手しましょう。

jp.tek.com

マニュアルを読むと、このような説明が出てきます。

CH<x>? Returns vertical parameters for the specified channel

それでは、実際にやってみましょう。ここで注意するのは、<x> の部分は適切な値に置き換えよという意味です。? が最後についているのは、問い合わせのコマンドということです。つまり、返り値があります。

In [6]: usb.query('CH1?')                                                       
Out[6]: '0;10.0000;"No probe detected";"";1.0000;"V";50.0000;"Other";0.0E+0;0.0E+0;1.0000E+9;DC;0.0E+0;0.0E+0;0;-2.9600;1.0000;"V";50.0000;""\n'

この読み方はマニュアルを参照するとして、例えば縦軸の分割幅は 1.0000 V であり、オフセットが -2.9600 V だということが分かります。カップリングは DC で終端は 50.0000 Ω です。

次に、電圧のオフセットを変更してみましょう。マニュアルによると、文法は CH<x>:OFFSet <NR3> および CH<x>:OFFSet? です。ここで小文字は省略可能という意味です。また <NR3>2.0E-3 などの指数表示を意味します。(<NR1> は整数値、<NR2> は小数です。)

In [15]: usb.write('CH1:OFFS 1.23E-1')                                                                                                                          
Out[15]: (18, <StatusCode.success: 0>)
In [16]: usb.query('CH1:OFFS?')                                                                                                                                 
Out[16]: '123.0000E-3\n'
In [17]: voffset_ch1 = float(usb.query('CH1:OFFS?')[:-1])                                                                                                       
In [18]: voffset_ch1
Out[19]: 0.123
In [20]: usb.write('CH1:OFFS %.2E' % voffset_ch1)

動作確認時を除いて、実際にプログラム中に書くときは省略せずに全て書くことをお勧めします。コマンドの可読性が増すからです。

次に波形を取得してみましょう。簡単のため、データフォーマットは ASCII にします。

In [31]: usb.write('WFMOutpre:ENCdg ASCii')
In [32]: usb.write('DATA:SOURCE CH1')
In [33]: data = usb.query('WAVFRM?')
In [34]: data[:250]                                                                                                                                             
Out[34]: '2;16;ASCII;RI;MSB;"Ch1, DC coupling, 1.000V/div, 4.000ms/div, 1000000 points, Sample mode";1000000;Y;LINEAR;"s";40.0000E-9;-20.0000E-3;0;"V";156.2500E-6;29.6960E+3;123.0000E-3;TIME;ANALOG;0.0E+0;0.0E+0;0.0E+0;27648,27904,27392,27648,27648,27904,27648'

あとは、マニュアルを読んでこのデータを波形情報に戻してみましょう。

PySerial

USB 端子を持っていても、VISA に対応していない機器があります。その多くは USB 端子の後ろに USB to Serial converter と呼ばれる、USB とシリアル通信(RS-232C)を変換するチップが搭載されています。このような機器は PyVISA ではなく PySerial を使って制御します。

また、USB 端子ではなく RS-232C 端子しかない機器もあるでしょう。これには、USB-RS232C の変換ケーブルを使用します。変換チップには様々なメーカーのものが存在しますが、FTDI 社製のものが一般的です。また FTDI 製であれば macOS でも Windows でも Linux でもデバイスドライバーのインストールなしに動作しますので、例えば Buffalo のこのケーブルは確実に動作します。

Mac に FTDI 製のチップを持つ周辺機器、もしくは FTDI 製チップの使われている変換ケーブルで RS-232C の機器を接続したときは、/dev/ 以下にデバイスファイルが作成されます。

例えば Keithley 社製のある機器の場合、FTDI のチップが使われているため次のように表示されます。表示されるデバイスファイル名はチップのシリアルナンバーにしたがって異なるものになります(Mac の場合)。

$ system_profiler SPUSBDataType
(略)
    USB 3.0 Hi-Speed Bus:

      Host Controller Location: Built-in USB
      Host Controller Driver: AppleUSBXHCI
      PCI Device ID: 0x1e31 
      PCI Revision ID: 0x0004 
      PCI Vendor ID: 0x8086 
      Bus Number: 0x14 

        USB HS SERIAL CONVERTER:

          Product ID: 0x6001
          Vendor ID: 0x0403  (Future Technology Devices International Limited)
          Version: 4.00
          Serial Number: FT123456
          Speed: Up to 12 Mb/sec
          Manufacturer: FTDI
          Location ID: 0x14100000 / 1
          Current Available (mA): 500
          Current Required (mA): 44
$ ls /dev/tty.usbserial*
/dev/tty.usbserial-FT123456

PySerial を入れると、同様に SCPI のコマンドで制御することができるようになります。

import serial
keithley = serial.Serial(port="/dev/tty.usbserial-FT123456",
                         baudrate=57600,timeout=1,writeTimeout=1)

keithley.write('*RST\n')
keithley.write(':SENS:FUNC "VOLT"\n')
keithley.write(':SOUR:FUNC VOLT\n')
keithley.write(':OUTP ON\n')

ここで、RS-232C の装置の場合は baudrate という値の設定をその機種ごとに揃えて必要があります(今の例の場合、57600)。装置のマニュアルに必ず書いてあるはずですので、読むようにしましょう。また使用する改行コードも機種ごとに異なるため、今の場合は \n を使用していますが、これもマニュアルで確認してください。

Anaconda で ROOT 入れたり

Anaconda 環境に ROOT を入れるときのメモ。1 年前に Mojave でやったときとちょこちょこ変わっているので、2020 年 6 月現在の以下の環境を想定。

  • macOS 10.15.4
  • Xcode 11.5
  • CMake 3.17.1(conda ではなく Homebrew から)
  • Anaconda3-2020.02-MacOSX-x86_64.sh

まず、Mac 用の Anaconda を落としてくる。 https://repo.anaconda.com/archive/Anaconda3-2020.02-MacOSX-x86_64.sh

適当に答えて進める。

$  chmod +x Downloads/Anaconda3-2020.02-MacOSX-x86_64.sh
$ ./Downloads/Anaconda3-2020.02-MacOSX-x86_64.sh

自動的に .zshrc に Anaconda の設定が書き込まれるのが好きじゃないので、自分の場合は関数で括って好きなときにAnaconda 環境に入るようにしている。

conda_init(){
# >>> conda initialize >>>
# !! Contents within this block are managed by 'conda init' !!
__conda_setup="$('/Users/oxon/anaconda3/bin/conda' 'shell.zsh' 'hook' 2> /dev/null)"
if [ $? -eq 0 ]; then
    eval "$__conda_setup"
else
    if [ -f "/Users/oxon/anaconda3/etc/profile.d/conda.sh" ]; then
        . "/Users/oxon/anaconda3/etc/profile.d/conda.sh"
    else
        export PATH="/Users/oxon/anaconda3/bin:$PATH"
    fi
fi
unset __conda_setup
# <<< conda initialize <<<
}

4.8.3 に update する。

$ conda_init
(base) $ conda update -n base -c defaults conda

好きな環境、例えば sstcam_root という環境を作る。このとき、-c conda-forge で channel を指定する必要あり。ROOT はここにあるので。

(base) $ conda create -n sstcam_root root -c conda-forge
(base) $ conda activate sstcam_root
(sstcam_root) $ which root.exe 
/Users/oxon/anaconda3/envs/sstcam_root/bin/root.exe
(sstcam_root) $ root
root [0] .q

以前は MacOSX10.9.sdk とかを落としてくる必要があったのだけど、今は要らなくなった。なので、Anaconda 側で Xcode についてくる SDK を勝手に環境変数に設定してくれる。これは base のときは空っぽで、次のように ROOT 環境のほうだけ。10.15 が指定されている。

$ conda_init 
(base) $ echo $CONDA_BUILD_SYSROOT

(base) $ conda activate sstcam_root 
(sstcam_root) $ echo $CONDA_BUILD_SYSROOT 
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk
(sstcam_root) $ ll $CONDA_BUILD_SYSROOT
lrwxr-xr-x  1 root  wheel  15 Mar 25 11:24 /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -> MacOSX10.15.sdk

これで、ACLiC も問題なく動く。ACLiC を動かすためにも、以前は SDK を自分で用意する必要があった。

(sstcam_root) $ root
root [0] .x aho.C+
Info in <TMacOSXSystem::ACLiC>: creating shared library /Users/oxon/./aho_C.so

Anaconda で入れるものではない library を CMake で build するとき、引数を追加する必要がある。

docs.conda.io ここを読むと、次のようにやる必要があるらしいのだけど(これは clang が Anaconda 環境のものが使用されてしまうため)、

(sstcam_root)$ cmake ../source -DCMAKE_INSTALL_PREFIX=$CONDA_PREFIX -DCMAKE_OSX_SYSROOT=$CONDA_BUILD_SYSROOT

実際には次のコマンドでちゃんと動いた。CMake のバージョンによるのかもしれない。

(sstcam_root)$ cmake ../source -DCMAKE_INSTALL_PREFIX=$CONDA_PREFIX

SYSROOTROOT は ROOT と関係ない。

この環境で例えば他に Python 関係のものを追加したい場合、conda-forge から入れるようにする必要がある。

(sstcam_root) $ conda config --env --add channels conda-forge
(sstcam_root) $ conda install scipy astropy matplotlib tqdm pandas numba cython scikit-learn

Studio.app と LDraw を使って LEGO 風のイベントディスプレイをスーパーカミオカンデ用に作る

同業の中の人がこんな企画をされていました。

あくまで一般向けのものであって、同業者が手を出すものではないと思うのですが、Python + matplotlib よりも ROOT だと早いよ、というのを言いたくなってしまったので、大人気ないことをしてしまうわけです。

さて、以前から LEGO ブロックで物理関係のことをやってみたいなとずっと思っていて、また LEGO の 3 次元データを扱うためのファイルフォーマットの 1 つである LDraw というものを最近知りました。LDraw のフォーマットは非常に簡単なので、これを使うとイベントディスプレイに使えます。
www.ldraw.org

また Studio.app という LEGO(風の)ブロック用の 3D ソフトウェアもあることを知り、これで LDraw をインポートできるということもわかりました。じゃあ、これらを組み合わせるとスーパーカミオカンデ(SK)のイベントディスプレイを LEGO 風にできるわけです。
www.bricklink.com

本当は SK ではなくて、自分のやっているチェレンコフ望遠鏡アレイ(CTA)のイベントディスプレイで遊ぶべきなのですが、同業の中の人の企画にまず乗っかってみようかな、と。

さて、結果です。

ROOT で 5 行で作る場合
$ root
root [0] TNtuple nt("nt", "nt", "ch:pe:t:x:y:z")
root [1] nt.ReadFile("Downloads/sample/multirings.0001.000015.csv")
root [2] nt.SetMarkerStyle(20)
root [3] nt.SetMarkerSize(0.5)
root [4] nt.Draw("z:y:x:pe", "", "col")

f:id:oxon:20200531121653p:plain
ROOT の NTuple で作ったもの

ROOT + Python で LDraw に出力したもの
$ ./sk2lego.py Downloads/sample/multirings.0001.000015.csv > event.ldr

f:id:oxon:20200531121744p:plain
LDraw に出力して、Studio.app でインポートしたもの

LDraw の形式は、こんな感じです。1 に続けて、色番号、座標と回転行列の要素、そして部品番号です。簡単ですね。

1 36 x y z Rxx Rxy Rxz Ryx Ryy Ryz Rzx Rzy Rzz 4073.dat

まあ、これだけだと実際に立体に組むことはできないので、東大 LEGO 部の作品に組み込むと格好良さそうな気がします。
http://www-sk.icrr.u-tokyo.ac.jp/pr/topics/2016/08/img/DSC_0984%20copy.jpg

sk2lego.py の中身です回転行列を使う必要があるので、それは ROOT を呼んでいます。

gist.github.com

愛知・岐阜県内 329 人の感染経路を可視化してみた

記事のタイトルは、9 年前の自分の記事からの「インスパイア」です。あの頃は、放射線量の時間変化や、空間分布を作っていました。福島県に親戚が住んでいたからです。いつでも逃げられるように、定量的な判断基準を持つための資料が必要でした。
oxon.hatenablog.com

雑感

今回の新型コロナウイルスでは、誰から感染するかも分からないし、逆に感染させてしまうかもしれないし、日本中、世界中のどこに行っても「逃げる」というのは困難です。自分一人が感染を防いでも社会全体の流れを止めることはできず、全員が同じように感染防止を目指さないと、逃げたくても逃げられないという、大変困難な状況です。我が家は自主的に保育園の利用を停止したり、小学校の始業式をサボったりしていますが、うちだけやったところで社会の流れを止められるわけもなく、焼け石に水と思いながら行動しています。

さて、毎日コロナのニュース、特に名古屋大学のある愛知県のニュースを観ていても、「本日は新規感染者が XX 人」とか「YY で起きた集団感染の濃厚接触者」とか 、そんな断片的な情報ばかり入ってきて、全然可視化できない。何が起きているか、頭の中で考えられない。どの集団感染がまだ続いているのか収束したのか把握し続けられない。合計感染者数の時間変化のヒストグラムを眺めても、はたして愛知はどういう状況かというのがちっとも分からない。そういう不満がありました。そこで、9 年ぶりに手を動かしてみることにしました。

図から分かること

愛知県は初期に感染者の急増があったのですが、その後増加が緩やかになりました。これを合計感染者数や、日毎の新規感染者数のヒストグラムで眺めていると何が起きているか見えませんでした。

(専門家や追いかけている人たちは知っていることでしょうが)今回の図で、2/14 と 2/29 に始まった初期の集団感染 2 件(スポーツジムとデイサービス)が関係者の努力で収まり、3 月上旬から感染経路不明の「孤発」事例が出続けているということが、よく見えてきました。感染防止や対策が徹底されていなかった頃の集団感染は仕方がないとして、やはり孤発事例が頻発し始めた時点で強い対策を取らないと、一気に広がるのだということが分かります。

3 月末からは岐阜県でも孤発事例が増え始め、愛知県警や岐阜シャルムの集団感染を除けば、感染経路不明のものが毎日 5〜10 件出るようになってしまいました。これは東京の 2 週間前と変わらない状況なので、自粛ムードの強くはなかった東海エリアでは、この先 2 週間で増え続けると覚悟する必要があります。

愛知の人口でも初期クラスターから感染経路不明の増大へ、という流れがはっきり見えるのですから、東京・大阪の大都市圏では、やはり「クラスター対策をすれば大丈夫」なんていうのは専門家の認識が甘かったということかと思います。もちろんクラスター潰しと追跡調査をするのは絶対に必要ですので、関係者の努力は大変ありがたいことです。しかし、あまりに感染力が強すぎた。追えない事例が多数出てしまう。それが今回の COVID-19 の大変なところであり、専門家ですら対策できると過信してしまった原因なのではないでしょうか。

このような感染の拡大は、おそらく愛知や岐阜に限ったことではないでしょう。まだ感染者の少ない小中規模の都市でも、気持ちを緩めていると孤発事例が徐々に増えていき、「あれ、もしかして危ない?」と思い始めた頃には後手後手の対応に回ってしまうということが起き続けるでしょう。

可視化したいもの、やりたいこと

  1. 感染者数の時間変化(それだけなら他にいくらでも転がっている)が終えること
  2. 集団感染なのか孤発事例なのかの切り分けができること
  3. 感染経路不明の事例の時間変化が一瞥して分かること
  4. 帰国者や外国籍が分かること
  5. 個々の集団感染がどのように広がり、収束したのかどうか、今も拡大し続けているのか分かること

以上の条件を満たすにはどうすれば良いかを検討し、普段は CERN の ROOT というデータ解析ライブラリを使うのですが、今回は Graphviz を使うことにしました。
www.graphviz.org

実際にどうやるか

Graphviz は複数のノード(node)をエッジ(edge)で接続し、うまいことグラフを描画してくれるソフトウェアです。ここで言う「グラフ」は、ネットワーク図などグラフであり、数学用語のグラフです。

何百人もいる感染者をノードとして配置し、感染者同士の接触をエッジとして表現するのは、手動でやるのは困難です。少なくとも何週間かは毎日この作業を継続する必要があると判断し、Graphviz で自動化することにしました。簡単に自動化できるだろうと踏んでいたのですが、実際は手動での微調整でかなり作り込んでやる必要がありました。

Graphviz をそのまま使うのではなく、CSV ファイルを Python で読み、Python 内で graphviz モジュールを利用することにしました。

graphviz.readthedocs.io

多数の感染者情報を自治体発表の HTML や PDF から収集するのは非常に大変なのですが、東海 3 県に限っては中京テレビが機械可読性の非常に高いデータ整理をしてくれていました。原発のときは文科省福島県の測定データを自分自身や有志の多くの方のご協力で Google Spreadsheet に手入力し、可視化も自分でするという作業をしていたので大変だったのですが、中京テレビのお陰で今回はその点、とても楽です。
www.ctv.co.jp

Graphviz で苦労したところ

日付を縦方向に揃える

Graphviz の基本的な考え方は「自動で全てのノードをいい具合に配置する」です。そのため、細かいノード位置の調整を人間がして、エッジだけ自動的に描かせるという思想にはなっていません。しかし今回は、ある日付に陽性確定した感染者を時系列で並べたいため、同じ陽性確定日の感染者は縦に整列させる必要があります。

そこで、今回は subgraph と rank という機能を使って、同じ陽性確定日の感染者は無理やり横位置を揃えることにしました。この場合、Graphviz は横位置を揃える作業は死守してくれるようです。例外なく、ちゃんとノードを揃えてくれました。

好きな場所に文字列を置けない

凡例や注意書きなど、グラフ中に多数のテキストを配置する必要がありました。しかし、調べた限りではそのような機能がありません。また Graphviz の描画エンジンとして dot を使用する場合、座標の指定なんかもできないようです(いくつかの描画エンジンを選べるが、前述の rank は dot で使える)。

そのため、図中の様々なテキストは、多数のノードを追加するという無理やりな手法をとっています。加えて、ノードの中に書き込むテキストは、テキスト中心位置とノード中心位置が同じになります。そうすると文字列を左揃えにすることができなくなるため、全角スペースを多数挿入するという荒技を使っています。

s.node('author', label=' ' * 25 + 'データ出典:https://www.ctv.co.jp/covid-19/person.html\n' + ' ' * 28 + '作成:@AkiraOkumura(名古屋大学 宇宙地球環境研究所 奥村曉)', shape='plaintext', fontsize='40', height='2.5')
ノードの縦位置が自動配置されてしまう

「自動配置されてしまう」と Graphviz に文句を言うのは、それが設計思想なので筋違いなのすが、今回の図では愛知県と岐阜県で区分けしたかったため、この自動配置には大変困りました。左→右で進むグラフの場合、追加したノードは下から上に並ぶようです。ただし追加した順通りになるわけではなく、グラフ形状にしたがって自動配置が Graphviz が最適と思うやり方で行われます。

そうすると、愛知県の感染者ノードの中に岐阜が混じってしまったり、その逆も起きます。そこで、手動にて非表示のエッジを多数追加することで、自分の意図した場所に無理やり置くことができるようになります。見えないエッジを追加することで、それらが接続するノードを近接した場所に置こうと Graphviz が機能するためです。しかしこれでも、自分の意図したものに近づくだけであって、Graphviz の挙動を完全に制御することはできません。

graph.edge('aichi134', 'dummy2020-03-19', style='invis')
graph.edge('aichi145', 'dummy2020-03-23', style='invis')
graph.edge('aichi151', 'aichi152', style='invis')
graph.edge('aichi155', 'aichi152', style='invis')
graph.edge('aichi155', 'aichi151', style='invis')
エッジ以外の線が描けない

これも Graphviz の設計思想として、そんなものは必要ないのは明らかですが、エッジ以外の線を自由に描くことができません。今回の場合、愛知と岐阜の県境を滑らかな線で描きたかったのですが、これも非表示のノードを多数配置しそれを点線のエッジで接続することで、無理やり実現しました。県境が滑らかでなくガタガタしているのはこれが原因です。

matplotlib などと連携できない

本当はヒストグラムなどを matplotlib かなんかで描いて、Graphviz の結果も一緒に並べるなんてことをやりたかったのですが、Pythongraphviz モジュールはそれ単体の世界で閉じているので、できませんでした。

エッジが多すぎると作図が発散する

集団感染が発生した場合、多くの感染者ノード同士を何本ものエッジで接続すると、描画結果が大変な場合になることがありました。具体的には岐阜県の飲食店シャルムの集団感染なのですが、ここをエッジで接続するのは諦めました。
f:id:oxon:20200409100712p:plain

ペスト (新潮文庫)

ペスト (新潮文庫)

  • 作者:カミュ
  • 発売日: 1969/10/30
  • メディア: ペーパーバック
銃・病原菌・鉄 上巻

銃・病原菌・鉄 上巻

Gist にきったない Python コードを置いておきます

人に見せるような綺麗なコードではないのですが、Graphviz を使う例として、誰かの役に立つかもしれません。
gist.github.com

Twitter のサムネイル用

f:id:oxon:20200409104542p:plain

オンライン講義の経験談

新型コロナウィルスの影響により、Zoom 等を利用した大学講義のオンライン中継をこの 4 月から始める大学教員の方が多くいらっしゃると思います。僕はこれまでに同様のオンライン中継を 4 年間継続してきました。どのような問題が発生するか、どういう心構えでいれば良いか、個人的な体験を少しまとめておきます。

1. 行なった講義の形態

  • データ解析とプログラミングの講義 ROOT 講習会 2019 · akira-okumura/RHEA Wiki · GitHub
  • 90〜120 分/回のオンライン講義、合計 5〜6 回/年を 4 年間継続(2016〜2019 年のそれぞれ 4〜6 月)
  • 対象者は学部 4 年生と修士 1 年生
  • 板書なしの全てスライド(PDF は事前配布)
  • 名古屋大学の学生 10〜15 名程度は対面で、その様子と Mac のスライド画面を全国 10〜15 大学に中継(リモート接続含め、合計参加者 90 名程度)
  • マイクとカメラは、スライドを上映している Mac を使用

f:id:oxon:20160531090119j:plain
ROOT 講習会の名古屋大学側の様子

2. 感じた問題点

2-1. まるで壁に向かって喋っているようだ

同じ部屋に 10〜15 名程度の学生がいたものの、講師の側からはオンライン接続先の学生の顔が見えません。講師側の映像さえ学生に届いていれば良いようにも思いますが、実際やってみるとそうではありません。

通常の講義では、目の前にいる学生と頻繁に目を合わせることによって、直前に話した内容が理解されているかどうか、講義の進度が早すぎないかどうか、そういうものを講師側が読み取ることができます。例えば首を傾げている学生がいたり、相槌を打っている学生がいたり、板書に対してノートの追いついていない学生がいたり、そういったかなりの情報を講師側は教壇から得ています。

しかし、相手の顔や様子が分からないとなると、ひたすら気にせず一方的に喋り続けるか、もしくは 3 分に 1 回くらい「ここまで大丈夫?よく分からないところある?」といちいち学生に聞き続ける必要があります。この際、これも通常の講義で相手の顔色さえ伺えれば大丈夫かどうか雰囲気で分かるものですが、オンライン接続だと「大丈夫です」と全員が声を上げてくれるわけでもなく、10 秒程度待っても質問がなければ次へ進むという、非常にテンポの悪い進め方になります。

2-2. 質問のタイミング

名古屋大学の物理学科では、対面の講義でもなかなか質問の手が上がらないのですが、これがオンライン接続となるとさらに質問へのハードルが上がるようです。まず、ネットワークには必ず遅延が発生します。そのため、学生が「今だ」と思って質問したとしても、講師側は既に次の話題を話し始めていて、互いにタイミングの狂うことがあります。

また、相手側のマイクが入っていない、音声入力が小さくて聞き取れないといった、海外とのオンライン会議に慣れた研究者でもいまだに発生する問題が、不慣れな学生には頻発します。

加えて、リモートで参加している学生は教室の雰囲気が分かりません。他の学生は分かっているのかどうか、自分が分からないのは音声が途切れ途切れで聞き逃しただけだからではないかなど、通常の講義に比べると質問に対する心理的障壁が高くなると思われます。

2-3. チャット

気軽に質問をしやすくするためチャット画面でも質問を受け付けましたが、まず、こちらの期待するタイピング速度を学生は持っていません。向こうがゆっくりキー入力している間に、講義はどんどん進んでしまいます。

また、チャットでの質問は短文になりがちなため、結局こちらから「こういう意味?」と聞き返す必要がある場合がほとんどで、音声で質問してもらった方がトータルで早いということがありました。

加えて、講師の側は講義を行うのに集中しています。チャット画面をにらめっこしているわけには行かないため、質問されていても気がつかないということもあります。

2-4. 音声、映像が途切れる

これはもう技術的に仕方ない話ですが、音声や映像が途切れたり、途中で会議アプリケーションを立ち上げ直したり、なぜか会議に入り直すことができなくなったりという問題が毎週、毎年発生しました。研究者同士の会議でもこれは発生するので、不慣れな学生相手ではもうどうしようもないと思います。

2-5. ホワイトボードが映らない

スライドだけで講義するにしても、たまにホワイトボードに手書きで説明する必要がどうしても出てきます。この場合、カメラがどこの輝度に合っているかに注意しないといけません。プロジェクターのスクリーンに輝度があっている場合、その横にあるホワイトボードに字を書いても、おそらく配信先の学生には暗すぎて見えないでしょう。講義室にいる人間の眼のダイナミックレンジと、中継カメラのダイナミックレンジは全く異なるということを意識する必要があります。

またスライドではなく黒板でやる場合、カメラの解像度が十分あるか、解像度が十分あったとしても、ネットワークが不安定な相手先でブロックノイズが発生せずに綺麗に見えているかを気にする必要があるでしょう。

また身振り手振りで何かを説明する場合、自分がカメラからはみ出してしまうかもしれません。ホワイトボードもカメラに収まっている視野外にまで文字を書いてしまうかもしれません。

2-6. スライドの行ったり来たり

十分よく練って作ったスライドでも、「さっき説明した通り…」のように、何枚か前のスライドに移動したり、「後で説明するつもりでしたが…」と先に進んだりと、行ったり来たりすることはよくあります。この場合、講義室では即座に移動した先のページが見えるわけですが、接続先の学生の手元ではすぐに画面が切り替わっていないかもしれません。音声だけは途切れずに流れた場合、何ページ目が講義室で示されているのか分からなくなる場合があります。

2-7. 講義室の学生の声が拾えない

参加する学生が全員リモート接続であれば良いですが、通信環境の準備できない一部の学生などは大学の講義室で講義に参加すると思われます。この場合、ノートパソコン内蔵のマイクは講師側に向かっていますので、教室の後ろの席から学生が質問した場合、講師には質問が聞こえるけれどマイクは音を拾わないという状況になります。この場合、講師が「今 XXX という質問が講義室で出ました」と復唱する必要があります。

2-8. 冗談が通じたかどうか、反応が分からない

まあ、これは想像がつくと思います。悲しいです。

3. 改善案

3-1. 質問しやすい環境づくりと質問の練習

初回の講義のときに、マイクが問題なく動いているかの動作確認も含め、質問の練習をさせてみてください。また、じゃんじゃん質問して良いのだ、講義を質問で止めても良いのだ、自分だけ音声が途切れたかもしれないなんて気にしなくて良いのだ、ということを学生に伝えてください。

3-2. スライド PDF は前日までに配布

スライドでやる場合、必ず PDF にして前日までに配布し、講義の開始までに先にダウンロードするように指示してください。50 MB くらいのファイルに最近は簡単になってしまうため、講義の直前に配布したり、講義中に URL を教えたりすると、PDF をダウンロードし終えない学生が出てきます。

3-3. スライドの注意点

必ずページ番号を各ページに入れてください。何ページ目を開いているのか、必ず分かるようにしましょう。また PowerPointKeynote でアニメーションは一切使わないでください。たまに切り替わる静止画を送るのはオンライン会議ソフトは得意ですが、動画を上手に送れるとは期待しないでください。また配布 PDF ではアニメーションは再生できません。間違っても PowerPointKeynote のファイルのまま学生に配布しないように。

3-4. いつもよりゆっくり

通常の講義の 1.5 倍くらい時間をかけて進む必要があると思ってやってください。音が途切れたり、質問のやり取りは、通常の講義よりかなり時間を食います。

3-5. 接続テストを何回も

院生とかに協力してもらって、事前に接続テストをしっかり行なってください。

3-6. 録画する

途中で接続できなくなったり音声や画面が途切れてしまう学生が必ず出ます。これを学生の責任とするのはあまりに酷なので、そのような学生が後で視聴できるよう、講義を録画してください。

3-7. 質問の声の大きさ

講義室で質問する学生には、マイクで音を拾いやすいよう、大きな声で質問するよう、事前に依頼してください。

3-8. 心を強く持つ

相手の顔が見えない状態で講義をするのは、かなり心の折れる作業です。僕はメンタル強い方だと思いますが、ここ数年間で一番辛い仕事でした。

3-9. 板書に相当するもの

手元の PC のスライドを画面共有する場合、iPadApple Pencil などを活用して、板書に替わるものを用意しておくと便利かもしれません。その場合、PC と iPad の両方から同じ会議に参加する形が便利ではないかと思います。

全部黒板を使っての講義の場合、それを全て iPad に置き換えるというのは難しいかもしれません。

3-10. 講義室ローカルの雑談で盛り上がらない

学生側が活発な講義の場合、質問や講師側の雑談から、少し話が脱線して講義室内にいる人だけで雑談が発生する場合があります。これはリモート参加者からすると苦痛でしかありません。マイクで音を十分拾えず、また雰囲気も分からないからです。ついつい講義室にいる学生に向かって話してしまいがちですが、リモート接続の学生が主たる聞き手だという意識を 90 分持続しましょう。