SageMath DockerをWindowsで使う

# 1. 概要

本記事はSageMath (with JupyterLab)をWindowsで簡単に使ってみたいという人向けの手順です（一応、初心者向け…）。以前の投稿

- [JupyterHubのminimalな構築手順](https://qiita.com/yksantaro/items/8f683bc9a8f4bef1d051)
- [自前のJupyterLab/HubでSageMath Docker Imageを利用する方法](https://qiita.com/yksantaro/items/c2516e85ddabcd84958b)

でも触れた通り、Jupyterについては会社の割と高速な演算用サーバーにVPN接続して利用していましたが、外出時にVPNが使えないこともあります。でもそんな状況でも数学合宿（？）とかあると、計算機代わりのSageMathは手元で動いてほしい…w

というわけで、これまで**見向きもしなかった標準的な使い方**をやってみることになったのでその記録を残しておきます。**Windowsで任意バージョンのGUI付きSageMath（特に新しいもの）を試したい人には最も簡単な手順のはず！**

以下の手順で作業します。

1. WSL (WSL2) のセットアップ
1. Dockerのセットアップ
1. SageMath DockerイメージのPull
1. SageMath Jupyterの起動
1. ブラウザーでJupyterにアクセス
1. Enjoy SageMath!

また、導入以外のいくつかの設定トピックについても最後に触れます。

# 2. 環境準備

WSL (Winodws Subsystem for Linux)はWindows上でLinuxを使うためのHyper-Vベースの仮想化環境です。前の記事でも書いた通りSageMath開発陣はネイティブのWinodowsサポートを切り捨てています(笑)。最終的にSageMathのDockerイメージ動けばよいので、Docker EngineさえあればSageMath Dockerが使えます。

Docker Engineを使うには次の2つの手段が基本的です。

- WSL(Linux)上でDocker Engineを使う
- [Docker Desktop](https://docs.docker.jp/docker-for-windows/index.html)を使う

自分はWSLのヘビーユーザーなので、迷わずWSLでのセットアップを選択しているにすぎません。Docker Desktopを利用したい人はDocker Desktopの[セットアップ手順](https://docs.docker.jp/docker-for-windows/install.html)に従えばよいはずです。どちらか好きな方を実施すればよいでしょう[^1]。というわけで、以降はWSLを基にした手順を示します。

[^1]: とはいうものの、SageMathはFilesystem Hierarchy Standardを前提としているシステムなのでボリュームマウントすることなどを考えると、Docker engine自体の導入プラットフォーム自体もこれに準拠している方がトラブルになりにくいでしょう。

## 2.1. WSLのセットアップ

WSLの導入については元ネタというか公式ドキュメントは以下の通りです。迷ったりした場合はこちらを参照のこと。

- [WSLを使用してWindowsにLinuxをインストールする方法](https://learn.microsoft.com/ja-jp/windows/wsl/install)
- [WSL開発環境を設定する](https://learn.microsoft.com/ja-jp/windows/wsl/setup/environment)

一応、手順を示します。（何も指定せずに実行すると、現在（2024年3月）のところ**Ubuntu 22.04がインストール**される模様）

1. **管理者権限**でPowerShellを起動し、以下コマンドを実行
   ```powershell
   wsl --install
   ```
1. PCの再起動
1. 再起動後、WSLを起動してユーザー/パスワード設定
1. パッケージアップデート（社内や学内環境でプロキシ設定が必要な人は[付録A.](#付録a-wslにおけるプロキシ設定)を参照）
   ```sh
   sudo apt update
   sudo apt full-upgrade
   ```

以上で最低限のセットアップは完了です。

## 2.2. Dockerのセットアップ

aptでDockerをインストールするだけ。
```sh
sudo apt install docker
```

本コマンドでインストールされるのはUbuntuが公式に用意しているDocker Engineです。（もしかすると、初期状態でインストール済みの可能性あり…）

Docker EngineについてはUbuntu公式ではなく、Docker公式の最新debパッケージを使いたいというこだわりの強い人はDockerの公式情報「[Install Docker Engine on Ubuntu](https://docs.docker.com/engine/install/ubuntu/)」に従ってインストールしてください。

次に、一般ユーザー（WSLのセットアップ時に作ったユーザー）でもdockerコマンドを実行できるようにユーザー（yksantaroとしましょう）をdockerグループに参加させます。

```sh
sudo gpasswd -a yksantaro docker
```

以上で環境準備は完了です。

# 3. SageMath DockerイメージのPull

SageMathのDockerイメージはSageの公式開発グループが公開してくれています。

https://hub.docker.com/r/sagemath/sagemath

ここからイメージをPullしてきます。Pullというのはイメージ（インストールファイル群を1ファイルにまとめたもの）のインストール作業みたいなものです。ネットワーク・インストールみたいなものでしょうか…　尚、インストールには**3GB超のディスク容量が必要**です。

## 3.1. イメージのPull

最新のSageMathをPullしたい場合は以下のコマンドを実行します。

```sh:最新(latest)のインストール
docker pull sagemath/sagemath:latest
```

バージョン指定する場合は以下のようにlatestでないタグ指定します。

```sh:バージョン10.3のインストール
docker pull sagemath/sagemath:10.3
```

インストールがうまく行ったら、以下のコマンドを実行してイメージが登録されていることを確認します。

```sh
$ docker image ls
REPOSITORY             TAG       IMAGE ID       CREATED         SIZE
sagemath/sagemath      10.3      6c435587f4f8   11 days ago     3.09GB
sagemath/sagemath      latest    6c435587f4f8   11 days ago     3.09GB
```

2024年3月現在、latestはバージョン10.3ですので、両者のIMAGE IDが一致しています（つまり実体は1つのイメージファイル）。  

## 3.2. イメージの削除

Pullしたイメージを削除（アンインストール）したい場合は、docker image lsで調べたIMAGE IDを指定して削除します。

```sh:IMAGE ID 6c435587f4f8 の削除
docker rmi 6c435587f4f8
```

削除するとSageMathのファイルがゴミを残さず削除されるので、**安全かつ簡単に好きなバージョンのSageMathを試すことができる**のです。  
削除がうまく行っていれば、docker image lsで当該イメージが表示されなくなります。

# 4. SageMath Jupyterの起動
## 4.1. ストレージに関する基礎知識（スキップ可）

Dockerを利用するにあたってストレージ（ファイルシステム）に関して基本的なことを理解しておいた方が良いので、簡単に解説をしておきましょう。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3133575/a5d6c9be-9428-3ab6-c291-48dbe8c22255.png" width=450px>

上図のようにWSLやDcokerはWindowsの標準的なファイルシステム（C:\hoge\fugaみたいなの）とは違って、それぞれ独立したファイルシステムとなっているので各々に対応したアクセス方法をとります。

**■WSLファイルシステムへのアクセス方法**  
WSLは、それ単体でWindowsからは独立したLinuxとなっています（Hyper-Vの機能）。ファイルエクスプローラーでLinuxユーザー（ここではyksantaro）のHOMEディレクトリにアクセスするには
```
\\wsl.localhost\Ubuntu\home\yksantaro\
```
を開きましょう。

Linuxですから、個人(yksantaro)の作業ファイルは基本的にはこのHOMEディレクトリ（WSL上では /home/yksantaro にあたる）内に配置します。

**■Docker (SageMath) ファイルへのアクセス方法**  
DockerはWSLの中でさらに仮想化された環境です（Dockerは一般的には準仮想化と呼ばれています）。WSLのファイルシステムからも独立しています。

Dockerは一般的には必要な機能（例えばSageMath）をイメージに閉じ込めて提供するという考え方の代物ですので、機能の利用が終わればファイルシステムごと終了して、**どれほどファイルに変更を加えていたとしても初期状態に戻ってしまいます**。

よってSageMathのユーザーファイル（例えばIPythonノートブック）などは**WSLファイル上に準備し、それをDcokerファイルシステムからアクセスして利用します**。（この方法をファイルやディレクトリの**ボリュームマウント**といいます）

例えば、SageMath Dockerのデフォルト・ファイルシステムでは /home 以下にはsageディレクトリしかありません（下図左）が、WSL上に作業ディレクトリ /home/yksantao/notebook を作成し、このディレクトリをボリュームマウントしてSageMath Dockerのファイルシステムからそのまま利用できるようにします（下図右）。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3133575/3e9e0f12-5c6e-d269-243e-1f1a8aa87fa8.png" width=650px>

SageMathでの作業結果（ファイル）をすべてこのディレクトリに保存すれば、Dockerを終了してもディレクトリ内のファイルはすべてそのまま残ります。これを**データの永続化**といいます。

## 4.2. SageMath Docker (with JupyterLab) の起動

**■想定環境**  
まず、以下の環境を想定します。

|                     | WSL(Linux)側 | SgaeMath側 | 
| :---:               | :---:        | :---:      |
| **作業用DIR**       | /home/yksantaro/notebook | /home/yksantaro/notebook <br> (ボリュームマウント) |
| **設定ファイルDIR** | /home/yksantaro/notebook/.sage | /home/sage/.sage <br> （ボリュームマウント）|

作業用DIRは作業用のディレクトリで、**SageMathの作業ファイル（IPythonノートブックファイル等）を配置します**。SageMath側でも同様のPathで扱いたいので、そのままSageMath内にボリュームマウントします。

設定ファイルDIRはSageMathのユーザー用設定ファイル（SageMathにより自動生成）が置かれます。SageMath Dockerでは設定ファイルは、sageユーザーHOMEディレクトリ直下の.sageに保存されると決まっているので、これをyksantaro/notebook以下に保存されるようにボリュームマウントします。UI (Jupyter) の見た目を変更したりすると、その変更が本ディレクトリ内のファイルに残されます。

**■いよいよ起動！**

1. 作業用DIRの作成
   ```bash
   cd                # $HOMEに移動
   mkdir notebook    # 作業用DIR作成
   ```
1. 設定ファイルDIRの作成
   ```bash
   cd notebook       # notebookディレクトリに移動
   mkdir .sage       # .sageディレクトリの作成
   ```
1. SageMath (sage-jupyter) を起動
   ```bash
   docker run -it --rm -p 8888:8888 -e TZ=Asia/Tokyo -u $(id -u ${USER}):$(id -g ${USER}) -v ${PWD}:${PWD} -v ${PWD}/.sage:/home/sage/.sage -w ${PWD} sagemath/sagemath:latest sage-jupyter
   ```
   オプションがいっぱい付いていますが、本節冒頭の想定環境通りに動かすためのものです。

起動がうまく行くと、以下のようなメッセージが出力されます。
```
                        : （省略）
                        :
[I 2024-04-01 07:34:00.159 ServerApp] Jupyter Server 2.7.3 is running at:
[I 2024-04-01 07:34:00.159 ServerApp] http://b6ae88473dd4:8888/tree?token=37997fe7c8c5aafc25336e2d7515c2d249db66c90e46b3c0
[I 2024-04-01 07:34:00.159 ServerApp]     http://127.0.0.1:8888/tree?token=37997fe7c8c5aafc25336e2d7515c2d249db66c90e46b3c0
[I 2024-04-01 07:34:00.159 ServerApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
[C 2024-04-01 07:34:00.162 ServerApp]

    To access the server, open this file in a browser:
        file:///home/sage/.local/share/jupyter/runtime/jpserver-97-open.html
    Or copy and paste one of these URLs:
        http://b6ae88473dd4:8888/tree?token=37997fe7c8c5aafc25336e2d7515c2d249db66c90e46b3c0
        http://127.0.0.1:8888/tree?token=37997fe7c8c5aafc25336e2d7515c2d249db66c90e46b3c0
[I 2024-04-01 07:34:00.180 ServerApp] Skipped non-installed server(s): bash-language-server, dockerfile-language-server-nodejs, javascript-typescript-langserver, jedi-language-server, julia-language-server, pyright, python-language-server, python-lsp-server, r-languageserver, sql-language-server, texlab, typescript-language-server, unified-language-server, vscode-css-languageserver-bin, vscode-html-languageserver-bin, vscode-json-languageserver-bin, yaml-language-server
```

以上のようにコンソールにアクセス用URL（後半のtoken部分は起動ごとに変化する）

**http://<foo>127.0.0.1:8888/tree?token=37997fe7c8c5aafc25336e2d7515c2d249db66c90e46b3c0</foo>**

が生成されます。以上でSageMath (with JupyterLab)の起動が出来ました。

## 4.3. 【オプション】起動手順のシェルスクリプト化

前節で実行した起動手順をシェルスクリプト(docker_sage_jupyter)にしておきましょう。

```bash:docker_sage_jupyter
#!/bin/bash

if [ ! -d ./.sage ]; then
        mkdir ./.sage
fi

docker run -it --rm -p 8888:8888 -e TZ=Asia/Tokyo -u $(id -u ${USER}):（）$(id -g ${USER}) -v ${PWD}:${PWD} -v ${PWD}/.sage:/home/sage/.sage  -w ${PWD} sagemath/sagemath:latest sage-jupyter
```

保存後、実行権限を与えておきます。

```bash
chmod 755 docker_sage_jupyter
```

本ファイルを作業用DIRに配置しておけば、作業用DIRに移動後、本スクリプトを実行するだけでよくなります。

```bash
cd noteboot             # 作業用DIRに移動
./docker_sage_jupyter   # SageMath起動
```

# 5. ブラウザーでJupyterにアクセス

## 5.1. 始め方

起動後コンソールに表示されたURLにブラウザーでアクセスをすると、以下のようにJupyterLabの画面が出ます（テスト用にSage_test.ipynbファイルを作った直後）。

![jupyterlab_startup.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3133575/7c67aaf0-3381-8b51-b3ae-6b5caefdfa86.png)

SageMath用のJupyter Notebookファイル（Sage_test.ipynb）を開いていくつか実行した結果は以下図の通り。

![sage_test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3133575/bbab191b-3051-3e35-aadd-af98a5cc58b8.png)

きちんと動いていますね。

## 5.1. 終わり方

**■ノートブックの閉じ方**  
開いたノートブックを閉じる（そのノートブック用のSageMath kernelも停止する）には上部のメニューバーから、  
**[File] → [Close and Shut Down Notebook]**  
を選択する。ノートブックのタブが閉じてHomeタブに戻ります。

**■SageMathおよびJupyterLabの停め方**  
SageMathおよびJupyterLab自体を停止するには上部のメニューバーから、
**[File] → [Shut Down]**  
を選択する。すべてのSageMath kernelおよびJupyterLab自体が停止します。（ブラウザーを閉じられる）  
WSLコンソールのDockerも終了し、Linuxのコンソールに戻ります。

**■強制終了のやり方**  
トラブルなどで強制終了したい場合は、WSLのコンソールで **[Ctrl + c]** を入力します。

```
[I 2024-04-01 09:22:30.650 ServerApp] Saving file at /Sage_test.ipynb
^C[I 2024-04-01 09:22:39.702 ServerApp] interrupted  # ここで [Ctrl + c] 入力
[I 2024-04-01 09:22:39.702 ServerApp] Serving notebooks from local directory: /home/yksantaro/notebook
    1 active kernel
    Jupyter Server 2.7.3 is running at:
    http://b9a2b68207be:8888/tree?token=6d224cd916f3e3fbbf8430619bc95128efe667cb54e88c37
        http://127.0.0.1:8888/tree?token=6d224cd916f3e3fbbf8430619bc95128efe667cb54e88c37
Shutdown this Jupyter server (y/[n])? y              # 終了して良いか？と聞かれるので[y]入力
[C 2024-04-01 09:22:42.814 ServerApp] Shutdown confirmed
/home/sage/sage/local/var/lib/sage/venv-python3.11.1/lib/python3.11/site-packages/tornado/platform/asyncio.py:252: DeprecationWarning: add_callback_from_signal is deprecated
  warnings.warn("add_callback_from_signal is deprecated", DeprecationWarning)
[I 2024-04-01 09:22:42.815 ServerApp] Shutting down 5 extensions
[I 2024-04-01 09:22:42.815 ServerApp] Shutting down 1 kernel
[I 2024-04-01 09:22:42.816 ServerApp] Kernel shutdown: 5da06686-3237-4b49-94fb-732c49a821bc
```

これで、何かあっても強制的に停止することができます。
<br>

Enjoy SageMath!!  
以上。

# 付録A. WSLにおけるプロキシ設定

WSLはHyper-V仮想化で実現しているためWindowsとは独立したOSで動いています。従って、Windowsのプロキシ設定は基本的には役に立ちません。（がしかし、ネットワークのルーティング設定はWindowsのものに従っている様子…）

基本的には2つを設定すれば概ね問題ないでしょう。 

**■APT設定**  
以下のファイルにプロキシ設定を記載する。  
（proxy.example.comの部分は自組織の設定に合わせる）
```:/etc/apt/apt.conf
Acquire::http::proxy "http://proxy.example.com:8080/";
Acquire::https::proxy "http://proxy.example.com:8080/";
```

**■個人の環境変数**  
プロキシ環境変数を参照するソフトウェアは概ねこの設定で動きます。問題がある場合は各アプリ毎に設定する。  
（proxy.example.comの部分は自組織の設定に合わせる）
```sh:~/.bashrc
export http_proxy="http://proxy.example.com:8080/"
export https_proxy="http://proxy.example.com:8080/"
export no_proxy="localhost,127.0.0.1,172.31.0.0/16,10.0.0.0/8"
```

# 付録B. SageMath Dockerコンテナにbashでアクセスしたい場合

本節の内容はやや玄人向けです。トラブル時に自身で解決するための基本手段の紹介です。

- **SageMath dockerをsageではなくbashで起動する**
  ```bash
  docker run -it --rm -p 8888:8888 -e TZ=Asia/Tokyo -u $(id -u ${USER}):$(id -g ${USER}) -v ${PWD}:${PWD} -v ${PWD}/.sage:/home/sage/.sage -w ${PWD} sagemath/sagemath:latest bash
  ```
  
  最後の起動パラメーターをsage-jupyterからbashに変更するだけ。  
  bashで起動後「sage -n jupyter」とか叩くと、普通にSageMathとJupyterLabが開始します。
- **起動中のsage-jupyterにbashで横から入る**
  起動中に何かトラブルがあって調べたいときなどに有効な手段です。起動中のSageMathコンテナ名を調べて、そのコンテナにbashでログインします。
  ```bash
  $ docker ps -a  # まずは起動中のSageMathコンテナ名（もしくはコンテナID）を調べる
  CONTAINER ID   IMAGE                    COMMAND                  CREATED          STATUS          PORTS                                       NAMES
  a89d089e8c10   sagemath/sagemath:10.3   "/usr/local/bin/sage…"   40 seconds ago   Up 39 seconds   0.0.0.0:8888->8888/tcp, :::8888->8888/tcp   quirky_greider
  
  $ docker exec -it quirky_greider bash  # 起動中のquirky_greiderにbashでログインする
  sage@a89d089e8c10:/home/yksantaro/notebook$ ps -a
      PID TTY          TIME CMD
       98 pts/0    00:00:04 python3
      425 pts/1    00:00:00 ps

  sage@a89d089e8c10:/home/yksantaro/notebook$ ls -l
  total 20
  -rw-r--r-- 1 sage sage 18379 Apr  1 09:22 Sage_test.ipynb
  
  sage@a89d089e8c10:/home/yksantaro/notebook$ exit  # 作業を終えたらexitでbash終了（SageMath自体は終了しない）
  ```
  
  SageMathファイルシステムに、調査に必要なツールが入っていない可能性がありますが、そもそもこういうコマンドを叩いて自分で調査できる能力があるのなら、その問題も解決できるでしょうw

# 付録C. 最近のSageMathについて

ここからは個人的な感想など…

1年ほど前だったかにSageMathの開発プラットフォームがGitlabからGithubに移行しました。移行に際してかなりのゴタゴタがあったのか、コンテナイメージの作成などが半年以上にわたって停滞したりといったこともありました。

また、この移行の影響なのか過去バージョンのドキュメントが現在もうまく読めない状態のまま（最新ドキュメント以外のリンクが切れたまま）です。というわけで、SageMathを取り巻く状況はもうしばらくは落ち着かないのではないかと思えますね…

さらに、私は対外発表などのためのグラフ作成などもSageMathでやっていますが、以下の2つの問題があって、最新版への完全移行を留保している状態です。

- [axes_labels are cut off in Sage-9.8 and newer but not in Sage-9.6](https://github.com/sagemath/sage/issues/35971)
- [Some 2-d plot questions](https://ask.sagemath.org/question/54462/some-2-d-plot-questions/)

plot()に関する問題は、ここしばらくはSageのバージョンアップが進むにつれて出現してきている印象もあります。内部で動作しているmatplotlibのバージョンアップにSageMathのplotコードが対応できていないのかもしれない。

そんなわけで、私は結局2年くらい前のリリースである9.7を今でもメインに使っています。