2017/08/31(木)C++で気軽にリアルタイムグラフ表示 (matplotlib.hpp)

動機

日頃、C++でいろいろ計算していて、計算結果をグラフにしたいことがよくあります。テキストで吐かせて、必要ならば加工して、gnuplotで表示というのが定番でしょうか。最近はpythonで動くmatplotlibを使うことも多いです。

計算が終わってからグラフにするならこれでいいのでしょうが、計算中に現在の状況をリアルタイムに見たい、ということもあります。30年前のBASICの時代なら、LINE (0,0)-(639,399)とかすればすぐに画面に線が出たので、こういうのがとても気軽に出来ましたが、今はなかなか面倒な気がします。誰もがOpenGLでさっと書ける、というわけにはいかないでしょう。

これを実現する方法として、あまり知られていませんがgnuplotをパイプで繋いでリアルタイム描画させる方法があります。例えば、「C言語でgnuplotを利用してグラフ表示」に例があります。kvライブラリにもひっそりとgnuplotを使ったライブラリが含まれています。しかし、この方法は描画するオブジェクトが増えると非常に遅くなってしまい、実用的とは言い難いものでした。

gnuplotでなくmatplotlibならいくらか速そうなので、それで出来ないかと以前から考えていました。で、pythonインタプリタをパイプで繋いでpythonコマンドを文字列で送り込む作戦で作ってみました。作ってみたら案外良さそうなので記事にしました。kvライブラリにも入れようかな。

プログラム

作ったファイルは一つ(matplotlib.hpp)です。
#ifndef MATPLOTLIB_HPP
#define MATPLOTLIB_HPP

#include <cstdio>

class matplotlib {
	FILE *p;

	public:

	bool open() {
		p = popen("python -c 'import code; import os; import sys; sys.stdout = sys.stderr = open(os.devnull, \"w\"); code.InteractiveConsole().interact()'", "w");
		if (p == NULL) return false;
		send_command("import matplotlib.pyplot as plt");
		send_command("import matplotlib.patches as patches");
		send_command("fig, ax = plt.subplots()");
		send_command("plt.show(block=False)");
		return true;
	}

	bool close() {
		send_command("plt.close()");
		send_command("quit()");
		if (pclose(p) == -1) return false;
		return true;
	}

	void screen(double x1, double y1, double x2, double y2, bool EqualAspect = false) const {
		if (EqualAspect == true) {
			fprintf(p, "ax.set_aspect('equal')\n");
		}
		fprintf(p, "plt.xlim([%.17f,%.17f])\n", x1, x2);
		fprintf(p, "plt.ylim([%.17f,%.17f])\n", y1, y2);
		fprintf(p, "plt.draw()\n");
		fflush(p);
	}

	void line(double x1, double y1, double x2, double y2, const char *color = "blue", const char *opt = "") const {
		fprintf(p, "ax.draw_artist(ax.plot([%.17f,%.17f],[%.17f,%.17f], color='%s', %s)[0])\n", x1, x2, y1, y2, color, opt);
		fprintf(p, "fig.canvas.blit(ax.bbox)\n");
		fprintf(p, "fig.canvas.flush_events()\n");
		fflush(p);
	}

	void point(double x, double y, const char *color = "blue", const char *opt = "") const {
		fprintf(p, "ax.draw_artist(ax.scatter([%.17f],[%.17f], color='%s', %s))\n", x, y, color, opt);
		fprintf(p, "fig.canvas.blit(ax.bbox)\n");
		fprintf(p, "fig.canvas.flush_events()\n");
		fflush(p);
	}

	void rect(double x1, double y1, double x2, double y2, const char *edgecolor = "blue", const char *facecolor = NULL, const char *opt = "") const {
		
		if (facecolor == NULL) {
			fprintf(p, "ax.draw_artist(ax.add_patch(patches.Rectangle(xy=(%.17f,%.17f), width=%.17f, height=%.17f, fill=False, edgecolor='%s', %s)))\n", (x1+x2)/2, (y1+y2)/2, x2-x1, y2-y1, edgecolor, opt);
		} else {
			fprintf(p, "ax.draw_artist(ax.add_patch(patches.Rectangle(xy=(%.17f,%.17f), width=%.17f, height=%.17f, fill=True, edgecolor='%s', facecolor='%s', %s)))\n", (x1+x2)/2, (y1+y2)/2, x2-x1, y2-y1, edgecolor, facecolor, opt);
		}
		fprintf(p, "fig.canvas.blit(ax.bbox)\n");
		fprintf(p, "fig.canvas.flush_events()\n");
		fflush(p);
	}

	void ellipse(double cx, double cy, double rx, double ry, const char *edgecolor = "blue", const char *facecolor = NULL, const char *opt = "") const {
		if (facecolor == NULL) {
			fprintf(p, "ax.draw_artist(ax.add_patch(patches.Ellipse(xy=(%.17f,%.17f), width=%.17f, height=%.17f, fill=False, edgecolor='%s', %s)))\n", cx, cy, rx*2, ry*2, edgecolor, opt);
		} else {
			fprintf(p, "ax.draw_artist(ax.add_patch(patches.Ellipse(xy=(%.17f,%.17f), width=%.17f, height=%.17f, fill=true, edgecolor='%s', facecolor='%s', %s)))\n", cx, cy, rx*2, ry*2, edgecolor, facecolor, opt);
		}
		fprintf(p, "fig.canvas.blit(ax.bbox)\n");
		fprintf(p, "fig.canvas.flush_events()\n");
		fflush(p);
	}

	void circle(double cx, double cy, double r, const char *edgecolor = "blue", const char *facecolor = NULL, const char *opt = "") const {
		ellipse(cx, cy, r, r, edgecolor, facecolor, opt);
	}

	void polygon(double *x, double *y, int n, const char *edgecolor = "blue", const char *facecolor = NULL, const char *opt = "") const {
		int i;

		fprintf(p, "ax.draw_artist(ax.add_patch(patches.Polygon((");
		for (i=0; i<n; i++) {
			fprintf(p, "(%.17f,%.17f),", x[i], y[i]);
		}
		
		if (facecolor == NULL) {
			fprintf(p, "), fill=False, edgecolor='%s', %s)))\n", edgecolor, opt);
		} else {
			fprintf(p, "), fill=True, edgecolor='%s', facecolor='%s', %s)))\n", edgecolor, facecolor, opt);
		}
		fprintf(p, "fig.canvas.blit(ax.bbox)\n");
		fprintf(p, "fig.canvas.flush_events()\n");
		fflush(p);
	}

	void save(const char *filename) const {
		fprintf(p, "plt.savefig('%s')\n", filename);
		fflush(p);
	}

	void send_command(const char *s) const {
		fprintf(p, "%s\n", s);
		fflush(p);
	}

	void clear() const {
		send_command("plt.clf()");
	}
};

#endif // MATPLOTLIB_HPP
以下は簡単なテストプログラム(test-matplotlib.cc)。
#include "matplotlib.hpp"

int main()
{
	matplotlib g;

	// initialize
	g.open();

	// set drawing range
	g.screen(0, 0, 10, 10);
	// aspect ratio = 1
	// g.screen(0, 0, 10, 10, true);

	g.line(1,1,3,4);
	g.line(1,2,3,5, "red");

	g.rect(4,6,5,8);
	g.rect(6,6,7,8, "green");
	g.rect(8,6,9,8, "black", "red");

	g.point(4,2);
	g.ellipse(8,2,2,1);
	g.circle(2,8,2);

	double xs[5] = {6., 7., 6., 5., 5.};
	double ys[5] = {4., 5., 6., 6., 5.};
	g.polygon(xs, ys, 5, "black", "yellow");

	g.line(1,3,3,6, "green", "alpha=0.2");
	g.line(1,4,4,5, "black", "alpha=0.5, linestyle='--'");
	g.point(4, 3, "red", "s=100");

	g.save("test.pdf");

	getchar();

	// finish drawing
	g.close();
}
普通にコンパイルして走らせると、matplotlibが使えるpythonがインストールされていれば
Screenshot.png

のように表示されます。非常に短いプログラムなのでソースを見るのが早いような気もしますが、以下、簡単に使い方を説明します。

基本

#include "matplotlib.hpp"

int main()
{
	matplotlib g;

	g.open();
	g.close();
}
これが最小限でしょうか。matplotlib.hppをインクルードし、matplotlibオブジェクトgを一つ作り、g.open()でウィンドウが開き、gに対していろいろ描画命令を発行し、g.close()でウィンドウを閉じます。

描画範囲

	g.screen(0, 0, 10, 10);
で、描画範囲を(x,y)=(0,0)と(x,y)=(10,10)を対角線とする領域に設定しています。
	g.screen(0, 0, 10, 10, true);
のようにすると、x座標とy座標のアスペクト比が1になります(正方形が正方形に描画される)。

線分

	g.line(1,1,3,4);
のようにすると点(1,1)から点(3,4)へ線分が引かれます。
	g.line(1,2,3,5, "red");
のように色を指定することも出来ます。色の指定方法は、"Specifying Colors"で詳細を見ることが出来ます。

長方形

	g.rect(4,6,5,8);
で、点(4,6)、点(5,8)を対角線とする長方形が描かれます。線分と同様に、
	g.rect(6,6,7,8, "green");
で色を指定できます。
	g.rect(8,6,9,8, "black", "red");
のように色を2つ指定すると、一つ目の色で枠線が描かれ、二つ目の色で中が塗りつぶされます。

	g.point(4,2);
(4,2)に点が打たれます。線分と同様に色を付けることも出来ます。

楕円

	g.ellipse(8,2,2,1);
中心が(8,2)、x方向の半径が2、y方向の半径が1であるような楕円を描画しています。長方形と同様に色を付けたり塗りつぶしたり出来ます。

	g.circle(2,8,2);
中心が(2,8)、半径が2の円を描画しています。長方形と同様に色を付けたり塗りつぶしたり出来ます。

多角形

	double xs[5] = {6., 7., 6., 5., 5.};
	double ys[5] = {4., 5., 6., 6., 5.};
	g.polygon(xs, ys, 5, "black", "yellow");
点(6,4),(7,5),(6,6),(5,6),(5,5)をこの順に結んだ五角形を表示しています。長方形と同様に色を付けたり塗りつぶしたり出来ます。

ファイルにセーブ

	g.save("test.pdf");
このように、その時点での画像をファイルにセーブできます。ファイル形式は拡張子で指定します。

画面消去

g.clear()で画面消去できます。座標系の設定もクリアされるようで、g.screen()からやり直す必要がありそう。

追加オプション

	g.line(1,3,3,6, "green", "alpha=0.2");
	g.line(1,4,4,5, "black", "alpha=0.5, linestyle='--'");
	g.point(4, 3, "red", "s=100");
matplotlibには、線の種類や線の太さなど、更に無数のオプションがあります。それらを全て引数として用意するのは面倒だったので、最後の引数に文字列で書くとそれがそのままmatplotlibの該当コマンドのオプションとして渡されるようにしました。上の例は、
  • alpha=0.2として薄く表示
  • alpha=0.5として、更に線種を変更
  • 点を大きく
したものです。g.send_command()を使うと任意のpythonコマンドを実行できますので、その気になれば更に複雑なことも可能です。

技術的な細かいこと

  • 最初にpopenしてるところ、単にpythonインタプリタを呼ぶのでなく妙な引数がついてますが、自分の環境ではpythonインタプリタをそのまま呼んだ場合標準入力を一行ずつ読んでくれなかったので、小細工しました。もっとスマートな方法募集中です。
  • 普通に書くと新しく何かを描画する毎に全体を再描画してしまいgnuplotと同様遅くなってしまったので、blittingとかいう技を使って再描画を抑制しているつもりです。しかし、matplotlibの構造を全く知らずに適当に実験しながら作ったので、とんでもなく的外れなことをしているかもしれません。
  • C++とlibpythonをリンクしてpythonインタプリタ自身をプログラム中に取り込んでしまう方法でも同じことが出来ます。しかし、コンパイル時のオプションが必要だったり気軽さが失われてしまうのと、描画の重さに比べてパイプのオーバーヘッドはあまり大きく無さそうなので、気軽なパイプで作ってみました。

オマケ

最後にオマケでLorezn方程式の初期値問題の解を計算しながら描画する例です。boost.ublasを使っています。
#include <boost/numeric/ublas/vector.hpp>
#include "matplotlib.hpp"

namespace ub = boost::numeric::ublas;

template <class T, class F>
void rk(F f, ub::vector<T>& init, T start, T end) {

	ub::vector<T> x, dx1, dx2, dx3, dx4, x1, x2, x3;

	T t = start;
	T dt = end - start;

	x = init;
	dx1 = f(x, t) * dt;
	x1 = x + dx1 / 2.;
	dx2 = f(x1, t + dt/2.) * dt;
	x2 = x + dx2 / 2.;
	dx3 = f(x2, t + dt/2.) * dt;
	x3 = x + dx3;
	dx4 = f(x3, t + dt) * dt;

	init = x + dx1 / 6. + dx2 / 3. + dx3 / 3. + dx4 / 6.;
}

struct Lorenz {
	template <class T> ub::vector<T> operator() (const ub::vector<T>& x, T t){
		ub::vector<T> y(3);

		y(0) = 10. * ( x(1) - x(0) );
		y(1) = 28. * x(0) - x(1) - x(0) * x(2);
		y(2) = (-8./3.) * x(2) + x(0) * x(1);

		return y;
	}
};

int main()
{
	int i, j;
	ub::vector<double> x, x_new;
	double t, t_new, dt;
	const char *colors[3] = {"blue", "red", "green"};

	matplotlib g;

	g.open();

	g.screen(0., -30., 10., 50.);

	x.resize(3);
	x(0) = 15.; x(1) = 15.; x(2) = 36.;
	t = 0.;
	dt = pow(2., -5);

	for (i=0; i < (1 << 5) * 10; i++) {
		x_new = x;
		t_new = t + dt;
		rk(Lorenz(), x_new, t, t_new);
		for (j=0; j<3; j++) {
			g.line(t, x[j], t_new, x_new[j], colors[j]);
		}
		x = x_new;
		t = t_new;
	}

	getchar();
	g.close();
}
これを実行すると、
Screenshot-lorenz.png

のようなグラフが計算しながら描かれます。

おわりに

短いのでコピペでも十分でしょうけど、ここに載せた3つのプログラムをzipであげておきます。

matplotlib.zip

見れば分かるように単純なことしかしていませんので、C++以外の他の言語でも似たようなことは簡単に出来ると思います。

お役に立てば幸いです。

2017/04/17(月)bash on Ubuntu on Windowsで遊んでみる (Creators Update編)

bash on ubuntu on windowsで遊んでみるの記事で、bash on Ubuntu on Windowsの紹介をしましたが、それがこの4月のWindows 10 Creators Updateで大きく改善したとのことで、再度入れて遊んでみました。

前回(Anniversary Update時)との差分は、
  • ubuntuのバージョンが14.04から16.04になった。
  • ターミナルが改善し、日本語が文字化けせずきちんと扱えるようになった。WindowsのIMEでの日本語入力も可能に。
  • ubuntuからwindows内のコマンドを呼び出したり、windowsからubuntu内のコマンドを呼び出したりできるようになった。
  • ifconfigやpingなどのネットワーク系のコマンドが動作するようになった。
あたりのようです。

条件はWindows10の64bitで、Creators Updateになっていることです。「スタート→歯車アイコン→システム→バージョン情報」で
version1703.png

のようにバージョンが1703になっていればCreators Updateになっています。まだだった場合でも強制的にアップデートすることもできます。

インストール方法は前回とほとんど変わっていませんが一応書いておきます。
  • 「スタート→歯車アイコン→アプリ→プログラムと機能→Windowsの機能の有効化または無効化」で、「Windows Subsysyem for Linux (Beta)」をチェックする。再起動。
    check-wsl.png

  • 「スタート→歯車アイコン→更新とセキュリティ→開発者向け」で、「開発者モードを使う」を、「サイドロードアプリ」から「開発者モード」に変更。再起動。
    devel-mode.png

  • 「スタートを右クリック→Windows PowerShell(管理者)」でPower Shellを起動し、bashとタイプ。"y"でダウンロードとインストールが始まります。数分かかります。ユーザIDとパスワードを聞かれてインストール完了。ここで入れたパスワードは"sudo"実行時に使います。
    install.png

  • 次回以降は、スタートメニューに「Bash on Ubuntu on Windows」が登録されているのでそこから起動できます。
    startmenu.png

なお、前回のAnniversary Update時にbash on Ubuntu on Windowsを入れていた場合は、自動で新しいものにアップデートされることはない模様です。必要なファイルがあればバックアップした上で、いったんPower Shell(管理者)で
lxrun /uninstall /full
で全削除し、「bashとタイプ」のところからやりなおすのが確実です。
uninstall.png

使い方は、前回のAnniversary Updateのときとほとんど変わっていません。日本語が文字化けしなくなり、またターミナルからwindowsのIMEを使って日本語入力ができるようになったので、ちょっとしたプログラミングの演習程度ならこのままで十分に使えるようになりました。

また、X serverをwindowsに入れてGUIを使うソフトウェアを動かすこともできます。bash on ubuntu on windowsで遊んでみるの記事で紹介した
  • gnuplot
  • firefox
  • lxterminal
  • Xアプリでの日本語入力
  • tex
など試してみましたが全て同じように動作しました。安定度はずっと高まっているように感じました。

2016/10/31(月)bash on Ubuntu on Windowsを試してみる

Windows10の夏の大型アップデート(Anniversary Update)で搭載された、Windows Subsystem for Linux (bash on Ubuntu on Windows)を使ってみたので、記録を残しておきます。

cygwinやmsys2など、Windows上でunixツールを使うためのものは以前からいろいろありますが、Microsoft本家が出してきたこいつは、「ubuntuのバイナリがそのまま動く」という点が今までと違います。使うための条件は、
  • Windows10の64bit版であること
  • Windows10のバージョン1607以降であること
です。バージョンは、「スタート→歯車アイコン→システム→バージョン情報」で確認できます。
version1607.png

8月のリリース以降、少しずつ時間をずらしながらWindows Updateを降らせていたようですが、そろそろほとんどのWindows10が1607になった頃ではないでしょうか。

インストール

インストール方法の情報は検索すればたくさん出てきますが、次のような手順です。
  • 「スタートを右クリック→プログラムと機能→Windowsの機能の有効化または無効化」で、「Windows Subsysyem for Linux (Beta)」をチェックする。再起動。
    wsl.png

  • 「スタート→歯車アイコン→更新とセキュリティ→開発者向け」で、「開発者モードを使う」を、「サイドロードアプリ」から「開発者モード」に変更。再起動。
    developermode.png

  • 「スタートを右クリック→コマンドプロンプト」でコマンドプロンプトを起動し、bashとタイプ。"y"でダウンロードとインストールが始まります。数分かかります。ユーザIDとパスワードを聞かれてインストール完了。
    install.png

  • 次回以降は、スタートメニューに「Bash on Ubuntu on Windows」が登録されているのでそこから起動できます。
    startmenu.png

アンインストール

いろいろ試しにパッケージを入れたりしてシステムが壊れることもあるかと思いますが、コマンドプロンプトで
lxrun /uninstall /full
と入れるときれいさっぱり削除できます。上の「bashとタイプ」のところからやりなおすことが出来ます。
uninstall.png

簡単な使い方など

  • 中身はubuntu 14.04です。いつものubuntuの作法通り、
    sudo apt update
    sudo apt upgrade
    
    で最新に更新しておきましょう。
  • これで、端末内で完結するような作業は大体できます。最初は最低限しかインストールされていないので、いつものubuntuの作法で必要なパッケージをインストールしましょう。とりあえず
    sudo apt install build-essential
    
    でCコンパイラなど最低限の開発環境を入れることをお勧めします。
  • ubuntu側からは、windows側のファイルが例えばCドライブなら
    /mnt/c/
    
    以下に見えます。逆にwindows側から見てubuntuのファイルシステムは
    c:\Users\(windowsユーザ名)\AppData\Local\lxss\ 
    
    にあり、このフォルダがubuntu側の"/"に対応しています。隠しファイルになっているので普通の状態では見えないかも知れません。恐らく、ubuntu側からwindowsのファイルシステムを操作することはOK、windows側からubuntuのファイルシステムを操作するのはNG、だと思われます。
プログラミングして楽しむだけならこれで十分と思われます。が、次のような問題点があり、生活の全てをこの環境で行なうのは難しそうです。
  • X windowを使うソフトウェアが動かない。gnuplotくらい使いたい!
  • (文字幅を正しく扱えないせいか)日本語が頻繁に文字化けする。
  • そもそも日本語が入力できない。

X環境を作る

買ったままのwindowsでソフトウェア追加無しにunix環境が使えるのがbash on ubuntu on windowsのメリットですが、windows用のX serverを入れてしまえば使えるソフトウェアが一気に増え、また日本語の問題も解決できる可能性があります。そこで、上記問題点を解決すべく、X serverを入れてみます。

X serverは有料、無料いろいろあると思いますが、いろいろ検索してみると無料では
の2つがよく使われているようです。今回はXmingの方を使ってみました。Xming X Serverによると、Public Domain Releasesという無料版と、Website Releaseという新しいが寄付が必要な版があるようです。今回はPublic Domain Releasesの方を使いました。
  • Xming 6.9.0.31
  • Xming-fonts 7.7.0.10
をダウンロードしてインストールしました。途中sshクライアントを入れるか聞かれますが、自分は不要だったので「Don't install SSH client」としました。

スタートメニューから「Xming」を起動します。(「XLaunch」の方だといろいろオプションを設定してから起動します。今回はデフォルトのままで十分。) すると、右下に
xming.png

のようなアイコンが出ます。これで、bash on ubuntu on windowsからの描画命令を受け止める準備が出来たことになります。動作チェックします。
sudo apt install x11-apps
sudo apt install x11-utils
sudo apt install x11-xserver-utils
といくつかのx11の基本アプリをインストールして、
DISPLAY=localhost:0.0 xeyes &
とxeyesを起動してみます。
xeyes.png

のようにマウスカーソルを追う目玉が表示されたら、正常に動作しています。

gnuplotを試してみましょう。
sudo apt install gnuplot-x11
としてインストールし、
DISPLAY=localhost:0.0 gnuplot
として起動します。
gnuplot.png

のように、ちゃんと動作しました。毎回「DISPLAY=localhost:0.0」とするのが面倒なら、
export DISPLAY=localhost:0.0
とすると、端末を閉じるまで有効になります。

驚くべきことに、日本語フォントを追加してやると、少しエラーが出るもののfirefoxを動かすことができます。
sudo apt install fonts-ipafont
sudo apt install firefox
firefox.png

日本語環境を整える

ここまでちゃんと動作するとなると、日本語の読み書きがまともにできないのが惜しくなってきます。そこで、少し頑張って環境を整えてみました。いろいろ試行錯誤した結果ではありますが、もっといい方法もありそうなので情報が欲しいところです。以下、自分が試した方法を書きます。X serverがwindows側にインストールしてあって、また上で書いたように、
sudo apt install fonts-ipafont
で日本語フォントを追加してあるものとします。

まず、端末は、windows側を捨ててXの方で動かすことにします。いろいろ試しましたが、lxterminalが良さそうでした。
sudo apt install lxterminal
でインストールし、
DISPLAY=localhost:0.0 lxterminal &
で起動します。起動後に「編集→設定」でフォントをMonoSpace 10からMonospace 15くらいにしてあげると見やすい感じになりました。
lxterminal2.png

windows側の端末を使わずにこちらを使うことにします。こちらからだと「DISPLAY=locahost:0.0」をいちいち打たなくてよくなります。windows側の端末を閉じると全部落ちてしまうので、アイコンにでもしておきましょう。

かな漢字変換のシステムを入れます。いろいろ試しましたが、uim-anthyが何とか動作しました。
sudo apt install uim uim-xim uim-anthy
のようにインストールします。そして、windows側のbashターミナルで、
DISPLAY=localhost:0.0 UIM_CANDWIN_PROG=uim-candwin-gtk uim-xim &
のようにかな漢字変換サーバを起動し、lxterminalの起動は
DISPLAY=localhost:0.0 XMODIFIERS="@im=uim" GTK_IM_MODULE=uim QT_IM_MODULE=uim lxterminal &
とします。これで、「半角/全角」キーで日本語入力ができるようになりました。
uimanthy.png

terminal起動時の設定が長いですが、このterminalから起動したものにはこの設定が伝わるので、ここからいろいろ起動することにすれば楽です。

その他もろもろ

他にもいろいろ入れてみました。

TeX環境。
sudo apt install texlive-lang-cjk
で簡単に入ります(ちょっと時間がかかります)。ついでに
sudo apt install evince
でpdfビューアも。
tex.png

java。
sudo apt install default-jdk
あちこちでjavaは動かないという記述を見かけましたが、普通に動いているように見えます。

自分はvimで十分ですが、もう少し普通のエディタを使いたいなら、
sudo apt install geany
あたりはいかがでしょうか。
geany.png

ま、windows側のお気に入りのエディタを使えば済むことではありますが。

vmwareなどの仮想化ソフトを使うよりずっと軽いのが嬉しいです。windowsとの分業がしやすいのも大きな利点かと。次はsshdなどサーバ系のソフトをいろいろ試してみたいと思います。

追記

上で書いたかな漢字変換サーバとlxterminalの起動を自動化するなら、例えばhome directoryの.bashrcの末尾に
if [ $SHLVL -eq 1 ]; then
  if DISPLAY=localhost:0.0 xset q > /dev/null 2>&1 ; then
    DISPLAY=localhost:0.0 UIM_CANDWIN_PROG=uim-candwin-gtk uim-xim &
    DISPLAY=localhost:0.0 XMODIFIERS="@im=uim" GTK_IM_MODULE=uim QT_IM_MODULE=uim lxterminal &
  fi
fi
のように書けばいいでしょう。最初に起動されたbashで、なおかつXが利用可能なら、かな漢字変換サーバとlxterminalを起動します。

次のwindows10の大型アップデートでubuntu 16.04になるとか日本語入力も普通に出来るようになるとか噂が聞こえてくるので、そのときにはここに書いたことの大半は無意味になってしまうかもしれません。

この記事は、次のページを参考に書きました。
貴重な情報を公開して下さった皆様に感謝します。

2016/08/03(水)半精度浮動小数点数に関する思考実験

半精度浮動小数点数というものがあります。よく使われている単精度(float, 32bit)、倍精度(double, 64bit)に対して、全長16bitと単精度の半分で浮動小数点数を表現するものです。IEEE754-2008でbinary16としてフォーマットが定められています。deep learningの隆盛とともに「精度が低くてもとにかく速く」計算するニーズが高まり、GPUでハードウェアサポートされるなど、最近注目を集めています(ような気がします)。

IEEE754-2008の半精度では、16bitを符号s(1bit)+指数部e(5bit)+仮数部m(10bit)に分割しています。指数部のオフセットは15で、従って正規化数は
x = (-1)s × 1.m × 2e-15
のように、非正規化数は
x = (-1)s × 0.m × 2-14
のように実数xと対応します。

ところで、URRという浮動小数点数の表現形式をご存知でしょうか。浜田穂積先生が80年代(IEEE754制定より前!)に提案された浮動小数点数の表現形式です。詳細は
を見ていただくとして、簡単に言えば、指数部と仮数部の区切りを可変にし、1に近い数(=指数部を表現するのに必要なbit数が少ない)ときには指数部を短くして仮数部を長くして精度を稼ぎ、非常に小さい数や非常に大きい数を表現するときには仮数部の長さを犠牲にして指数部に長いbitを割り当てる、というものです。このとき、何も考えずに指数部と仮数部を結合してしまうとその区切りが分からなくなってしまいますが、そこは指数部を表現するのに「bit列の末尾が分かるような自然数の表現方法」を用いることで解決します。例えば、Eliasのガンマ符号デルタ符号といった符号化の方法がよく知られています。

さて、半精度浮動小数点数は、bit数が少ないこともあって表現できる数値の範囲が非常に狭く、簡単にアンダーフローやオーバーフローを起こしてしまいます。正の最大数は何と65504です。正の最小数は、精度を保っている正規化数で2-14≃6.1×10-5、非正規化数まで考えても2-24≃5.96×10-8にすぎません。

そこで、URR的な考え方を用いて16bit浮動小数点数を構成したらどうなるか考えてみました。URRは-infやNaNが無いなど、現代のIEEE754に慣れた我々には使いにくいところもあるので、指数部と仮数部の区切りを可変にするという思想はそのままで、適当にフォーマットを定めます。指数部は、Eliasのデルタ符号を用いることにします。デルタ符号は1,2,3,…の自然数しか表せないので、指数部とデルタ符号で表す数値を
デルタ符号12345255256508509510511
指数部0-11-22127-128-254±0±infNaN
デルタ符号の長さ13355141515151515
のように対応させることにしました。指数部の最後の3つを特殊な数に割り当てています。仮数部は、IEEE754に倣って先頭の1を格納しない「ケチ表現」にします。このフォーマットとIEEE754-2008のbinary16で、指数部と仮数部の長さの関係を表にしてみます。
提案方式の仮数部長IEEE754-2008の仮数部長
2-2541-
2-1281-
2-1272-
2-2461
2-2362
2-2263
2-1669
2-15710
2-14711
2-8711
2-7811
2-4811
2-31111
2-21111
2-11311
201511
211311
221111
231111
24811
27811
28711
214711
215711
2166-
21272-
21281-
22531-
これを見ると、1付近では仮数部が長くなり、1から離れると徐々に仮数部が短くなっていき、(精度は低いものの)小さな数から大きな数まで表現できていることが分かります。全長16bitなどという極端に厳しい場面でこそ、このようなフォーマットが生きると思うのですがいかがでしょうか。

もちろんハードウェアのサポートが無くソフトウェアエミュレーションでは速度は絶望的ですが、将来このような優れたフォーマットが気軽に使えるようになればいいなと思っています。FPGAとかで作って遊んだりできないかなあ。

2016/05/21(土)カシオの関数電卓の奇妙な挙動

カシオの関数電卓をいじっていて、とても奇妙な挙動に気づいたので記録を残しておきます。使用したのはfx-5800Pという機種で、内部演算精度は10進数15桁です。結論から書くと、次のような現象を発見しました。
123456789123456 - 123456789121111 = 2345
123456789123456 - 123456789123111 = 345
123456789123456 - 123456789123411 = 0 (45でない!)
非常に近い2つの数同士の引き算を行なうと、「桁落ち」と呼ばれる有効数字の減少が発生します。これは有限精度の計算を行っている以上仕方のないことですが、どうやらこの関数電卓では、
「減算で桁落ちが発生した結果有効数字が2桁以下になったとき、その数を強制的に0に書き換える」
という仕様になっているようです。桁落ちが起きるような厳しい計算こそ高精度な電卓の助けが欲しいところなのに、そこでわざわざかろうじて残った情報を捨て去ってしまうというのは極めて不可解な仕様だと感じます。

この仕様で困ってしまう場面はいくらでもありそうですが、一つ例を作ってみました。2次方程式
0.9999996 x2 - 2x + 1.0000004 = 0
の解をいわゆる2次方程式の解の公式で計算してみます。判別式の値は、
4 - 4 × 0.9999996 × 1.0000004
= 4 - 3.99999999999936
= 0.00000000000064
= 6.4 × 10-13
ですが、この値が
4 - 3.99999999999936 = 0
となってしまいます。3.99999999999936を正しく格納できる仮数部を持っているにも関わらず、です。実にもったいない仕様だとは思いませんか。素直な10進数15桁の演算ならば、
sqrt(6.4 × 10-13) = 8 × 10-7
x1 = (2 - 8×10-7)/2/0.9999996 = 1
x2 = (2 + 8×10-7)/2/0.9999996 ≈ 1.000000800000032
のようにほぼ正確に計算できたはずなのに、
x1 ≈ 1.00000040000016
x2 ≈ 1.00000040000016
のような重解になってしまいます。また、手作業で解の公式を使って計算した場合のみならず、関数電卓に組み込まれた2次方程式ソルバーを使っても全く同じ計算結果が得られました。すなわち、ユーザに直接見える部分のみならず、内部の演算ルーチンそのものがこの仕様に蝕まれていることは確実と思われます。

この仕様はカシオfx-5800Pに特有のものなのか、他の機種にも見られるものなのか、ヨドバシカメラの関数電卓売り場の展示機を触って調べてみました。すると、
機種名仮数部の桁数桁落ち時に捨てられる桁数
カシオ fx-5800P/JP900/FD10Pro/375ES/72F152
キヤノン F-789SG182
キヤノン F-715SA163
シャープ EL-509M141
のようになりました。試した限りの全ての関数電卓で、桁数の違いこそあれこのような「過桁落ち」処理がなされているようです。全く理解できないのですが、どなたか事情をご存知なら教えて欲しいものです。
OK キャンセル 確認 その他