JupyTeX(LaTeX版Jupyter)を作った

この記事はTeX&LaTeX Advent Calendar 16日目の記事です.

はじめに

Jupyterとは

Jupyterとはブラウザ上で起動するREPL(Read-eval-print loop)です. 記述したプログラムと実行結果が逐次記録されていくので,過去のコードを見返したり変更できる便利なツールです.

jupyter.org

現在は様々な言語に対応していますが,元々はPython用のツールだったみたいです.

作ったもの

今回はLaTeX版Jupyter,名付けてJupyTeXを作りました. これは

JupyterでLaTeXが使えるようになるプラグイン

ではなく,

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に置き換わります. こんな感じにタイプセットできます.

f:id:muscle_keisuke:20171216164649j:plain

tcolorbox

Jupyterみたいな見た目を作るのに使います.様々な枠を作ることができるパッケージです. 枠線,枠内の色付けはもちろん,影も様々なタイプでつけられ, TikZと連携ができるのでデザインの自由度は高いです.

今回は

tcolorboxによる装飾表現(TeXユーザの集い2015)

tcolorboxの基本 - 物理とTeXに関する話題

http://texdoc.net/texmf-dist/doc/latex/tcolorbox/tcolorbox.pdf

を参考に枠部分のソースを書きました.

実際に作る

デザイン

完全にREPLでJupyterみたいなシステムにするのは無理です. なので,タイプセット後の見た目を似せることにしました.

  • TeXファイル内にpythonのコードを書ける
  • pythonのコードを追加してタイプセットするとコードと実行結果がセットで描画される

といった感じにできるといいですね.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}

PDF f:id:muscle_keisuke:20171216032011j:plain

コード&実行結果表示の仕組み

コードと実行結果表示の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
  • ソースコードが乗る灰色の内枠,inputframebox
  • 余白を制御するための実行結果が乗る透明な枠, outputframebox
outframebox

この枠を作っていきます.

f:id:muscle_keisuke:20171216132321j:plain

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の余白
}%

こんな感じになります.

f:id:muscle_keisuke:20171216165514j:plain

inputframebox

この枠を作っていきます.

f:id:muscle_keisuke:20171216135121j:plain

コード表示部分,もとい入力部分は灰色の枠の隣にIn [ n ]:という表示があります. この表示は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}}}

こんな感じになります.

f:id:muscle_keisuke:20171216165535j:plain

outputframebox

この枠を作っていきます. f:id:muscle_keisuke:20171216154259j:plain

透明ですが,この枠も重要です.実行結果をそのまま表示すると, 垂直方向において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,}

できあがったもの

見た目とシステムができたのでスタイルファイルにまとめます.

JupyTeX

それでは,このスタイルファイルを読み込んで使っていきます.

\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}

タイプセットした結果はこんな感じです.

f:id:muscle_keisuke:20171216170131j:plainf:id:muscle_keisuke:20171216170135j:plain

実際のPDFもGoogle Driveに置いておきます.

main.pdf - Google ドライブ

見た目はよくできるのではないでしょうか

できていないところ

ここまで作るまでも結構大変だったですが,それでもできていないところがたくさんあります.

  • nextframeコマンドで次の入力にいきたいのにできてない
  • pythonのソースコードを入力する時はインデントを開けることは許されない
  • .latexmkrcの書き方がわからない
    • uplatex -> pythontex -> uplatex -> dvipdfmx を一々やらなければならない
  • ソースコードの表示部分が更新されない

いろいろありますが,Advent Calendarに間に合わなかったら本末転倒なので妥協しました.

おわりに

jupytex環境の実用性

LaTeXでJupyterを実装することは需要がないですが, jupytex環境単体であれば,学校のレポートとかに使えるかもしれません*1. というわけで一応Githubのリポジトリリンクを貼っておきます.

github.com

LaTeXニューラルネットは?

本当は,こっちのAdvent Calendarに「LaTeXで実装するニューラルネット」を載せたかったのですが,まだできていないので,こちらを載せました. LaTeXニューラルネットは

学生 Advent Calendar 2017 - Adventar

か

Muroran Institute of Technology Advent Calendar 2017 - Adventar

に載せるかもしれません.

できなかったら載せません!

*1:ま,うちの学校は演習でPython使うことはないんですが.