この記事はTeX&LaTeX Advent Calendar 16日目の記事です.
はじめに
Jupyterとは
Jupyterとはブラウザ上で起動するREPL(Read-eval-print loop)です. 記述したプログラムと実行結果が逐次記録されていくので,過去のコードを見返したり変更できる便利なツールです.
現在は様々な言語に対応していますが,元々はPython用のツールだったみたいです.
作ったもの
今回はLaTeX版Jupyter,名付けてJupyTeXを作りました. これは
ではなく,
LaTeXで実装したJupyter
です. 需要はありません!
そもそも,私,Jupyter使ってな(ry.
作り方
作り方は簡単.
- PythonTeX
- tcolorbox
があればできます.
PythonTeX
PythonTeXはLaTeXで組版する文書内にPythonコードを埋め込み,実行結果を表示させることができるパッケージです. TeXLiveにはデフォルトで入っています. 動作にはPygmentsが必要なのでインストールします.
pip install pygments
これで使えます.
実際に使う例を示します. 第一引数から第二引数までの全ての整数の総和を表示するソースです.
\documentclass{article} \usepackage{pythontex} \newcommand{\mysum}[2]{% $\displaystyle\sum_{k=#1}^{#2} k = \py{sum(range(#1, #2+1))}$}% \begin{document} \mysum{1}{100} \end{document}
タイプセットは
でできます.適宜latex は platexやuplatexに置き換わります. こんな感じにタイプセットできます.
tcolorbox
Jupyterみたいな見た目を作るのに使います.様々な枠を作ることができるパッケージです. 枠線,枠内の色付けはもちろん,影も様々なタイプでつけられ, TikZと連携ができるのでデザインの自由度は高いです.
今回は
tcolorboxによる装飾表現(TeXユーザの集い2015)
http://texdoc.net/texmf-dist/doc/latex/tcolorbox/tcolorbox.pdf
を参考に枠部分のソースを書きました.
実際に作る
デザイン
完全にREPLでJupyterみたいなシステムにするのは無理です. なので,タイプセット後の見た目を似せることにしました.
といった感じにできるといいですね.LaTeXのソースとタイプセット後のPDFの理想は以下みたいな感じです.
\begin{jupytex} print("hello") \nextframe a = 5 b = 10 c = a+b print(c) \nextframe from matplotlib import pyplot as plt import numpy x = numpy.arange(0, 10, 0.1) y = numpy.cos(x) plt.plot(x,y) plt.show() ... \end{jupytex}
コード&実行結果表示の仕組み
コードと実行結果表示の2つでコードを使うので,コード自体を変数か何かに格納する必要があります. 今回はjupytex環境内にソースを書くので,ファイルに保存して読み出すことにしました. 記述したコードをファイルに保存するのには\VerbatimOut及び\endVerbatimOutを使います.
\VerbatimOut{<ファイル名>} 何かしらのテキスト \endVerbatimOut
で特定のファイルに保存することができます. filecontents環境も同様の用途で使える環境ですが,今回は自身の環境定義の中で行うので,\VerbatimOutコマンドを使いました.
ソースコードの表示
ソースコードの表示は \inputpygmentsコマンドでできます.シンタックスハイライト機能もあります.
\inputpygments{python}{<ファイル名>}
実行結果の表示
とても大変でした.PythonTeXはTeXファイル中に書いたpythonのコードを読んで実行結果の表示はできますが, 現在のバージョンでは,pyファイルから読み込んで実行結果を描画するコマンドがありません. 今回はpyファイルを実行するpythonのコマンドをTeXファイル中に書いてPythonTeXで実行することにしました. pythonコード中にシェルなどを実行するにはsubprocessパッケージを使います.
\begin{pycode} import subprocess def run_and_typeset(fname): print(subprocess.check_output(['python', fname]).decode("utf-8")) pytex.add_dependencies(fname) \end{pycode}
これでrun_and_typeset関数を実行した時に引数に取ったfnameの実行結果が表示されます. 続いて,ファイル名を引数に取るpythonソースの実行結果を表示するコマンドを以下のように定義します.
\edef\runandtypeset#1{\pyc{run_and_typeset("#1")}}
jupytex環境を定義する
\newcounter{pyfilenumber} \setcounter{pyfilenumber}{0} \NewDocumentEnvironment{jupytex}{}% {% \edef\currentfile{jupytex\arabic{pyfilenumber}.py}% \VerbatimOut{\currentfile}% }% {% \endVerbatimOut% \inputpygments{python}{\currentfile}% \expandafter\runandtypeset\expandafter{\currentfile}% \refstepcounter{pyfilenumber}% }
NewDocumentEnvironmentの行からjupytex環境の定義になります. その前の2行はpythonのコードを保存するファイル名をユニークにするためのカウンタになります. beginすると,currentfileというマクロにpythonのコードを保存するファイル名が入り,\VerbatimOutが展開されます. endすると,\endVerbatimOutが展開され,環境内に書いたpythonコードがcurrentfileに保存されます. その後,inputpygmentsコマンドで今保存したファイルを読み出し,ソースコードを表示します. runandtypesetコマンドで実行結果を表示します.最後にrefstepcounterコマンドでファイルの番号をインクリメントします.
枠をデザインする
tcolorboxで枠を作ります.
作る枠は2つです.
outframebox
この枠を作っていきます.
tcolorboxはオリジナルの枠を定義できるnewtcolorboxというコマンドがあります. これを使ってoutframeboxコマンドを定義します.
\newtcolorbox[]{outframebox}{% enhanced, % 影を付けるのに必要なオプション fuzzy halo=1.8pt with black!30, % 枠の周りにうっすら影を付ける breakable=true, % ページを跨いで枠を表示する colback=white, % 背景色は白 boxrule=0.1pt, % わずかに枠線を付ける top=0mm, % 枠上部の余白なし bottom=0mm, % %枠下部の余白なし right=0mm, % 枠右部分の余白なし left=0mm, % 枠左部分の余白なし arc=0pt, % 枠の角は丸みなし boxsep=10mm, % 枠周り全体的に10mmの余白 }%
こんな感じになります.
inputframebox
この枠を作っていきます.
コード表示部分,もとい入力部分は灰色の枠の隣にIn [ ]:という表示があります. この表示はtcolorboxのoverlay機能とtcolorboxが持っているカウンタを使って実現します.
\newtcolorbox[auto counter]{inputframebox}[1][]{% 第一オプション引数はカウンタの初期値 enhanced, tcbox raise base, % In [ n ] の表示を合わせる為 breakable=true, frame hidden, % 枠線なし top=0mm, bottom=0mm, right=0mm, left=10mm, arc=1pt, boxsep=10mm, overlay={\begin{tcbclipinterior} % overlay機能使う \fill[white] % 枠の左部分を In [ n ] のために確保 % TikZのノード形式で描画 % thetcbcounterはinputframeboxが呼ばれる度にインクリメントされる ([xshift=10mm]frame.south west) rectangle node[text=blue,] {In[\thetcbcounter]:} (frame.north west); \end{tcbclipinterior}}}
こんな感じになります.
outputframebox
この枠を作っていきます.
透明ですが,この枠も重要です.実行結果をそのまま表示すると, 垂直方向においてIn [ n ] と同じ位置に実行結果が来てしまうからです.
\newtcolorbox[]{outputframebox}{ enhanced, % breakableの中にbreakableを入れる時のオプションとbreakする位置の調整 enforce breakable=true,shrink break goal=15mm, colback=white, frame hidden, top=0mm, bottom=0mm, right=0mm, left=10mm, boxsep=0mm,}
できあがったもの
見た目とシステムができたのでスタイルファイルにまとめます.
それでは,このスタイルファイルを読み込んで使っていきます.
\documentclass[uplatex,dvipdfmx]{jsarticle} \usepackage{jupytex} \pagestyle{empty} \begin{document} \begin{outframebox} \begin{jupytex} print(30+50+580+558+578+73) \end{jupytex} \begin{jupytex} print("Hello") \end{jupytex} \begin{jupytex} print("dodododododo") \end{jupytex} \begin{jupytex} for i in range(1,2000): if i%3 == 0 and i%5 == 0: print("FizzBuzz") elif i%3 == 0: print("Fizz") elif i%5 == 0: print("Buzz") else: print(i) \end{jupytex} \end{outframebox} \end{document}
タイプセットした結果はこんな感じです.
実際のPDFもGoogle Driveに置いておきます.
見た目はよくできるのではないでしょうか
できていないところ
ここまで作るまでも結構大変だったですが,それでもできていないところがたくさんあります.
- nextframeコマンドで次の入力にいきたいのにできてない
- pythonのソースコードを入力する時はインデントを開けることは許されない
- .latexmkrcの書き方がわからない
- uplatex -> pythontex -> uplatex -> dvipdfmx を一々やらなければならない
- ソースコードの表示部分が更新されない
いろいろありますが,Advent Calendarに間に合わなかったら本末転倒なので妥協しました.
おわりに
jupytex環境の実用性
LaTeXでJupyterを実装することは需要がないですが, jupytex環境単体であれば,学校のレポートとかに使えるかもしれません*1. というわけで一応Githubのリポジトリリンクを貼っておきます.
LaTeXニューラルネットは?
本当は,こっちのAdvent Calendarに「LaTeXで実装するニューラルネット」を載せたかったのですが,まだできていないので,こちらを載せました. LaTeXニューラルネットは
学生 Advent Calendar 2017 - Adventar
か
Muroran Institute of Technology Advent Calendar 2017 - Adventar
に載せるかもしれません.
できなかったら載せません!