2017/11/08(水)kv-0.4.43とAVX-512による区間演算

久しぶりにkvライブラリを0.4.43にアップデートしました。

今回の主なアップデートは2つです。一つは、
の記事で書いたライブラリをkvに入れて、誰も使ってなかったと思われるgnuplot.hppを削除しました。

もう一つが重要で、AVX-512を使った高速な区間演算の実装です。AVX-512は、Intelの最新のCPUに搭載されている機能で、現時点では、
  • Xeon Phi Knights Landing
  • Skylake-X (Core i9 7900Xなど)
  • Skylake-SP (Xeon Platinum/Gold/Silver/Bronze など)
の限られたハイエンドCPUにしか搭載されていません。2018年末に予定されているCannonlakeで一般のCPUに搭載される予定になっています。AVX-512はいわゆるSIMD命令で、現在のAVX2 (256bitレジスタ) を更に拡張してレジスタ長を512bitとし、doubleなら8つのデータを同時に処理できるようになります。

ところで、AVX-512にはひっそりと(?)、「命令そのものに丸めモード指定を含んでいるような加減乗除と平方根」が搭載されました。精度保証付き数値計算で非常に重要な区間演算には、上向き丸めや下向き丸めでの加減乗除と平方根が必要です。従来は、演算を行なう前に丸めモードの変更命令を発行しており、これが高速化の妨げになっていました。直接丸めモードを指定できる演算を使えるならば、高速化が期待できます。具体的には、intrinsic命令で
  • _mm_add_round_sd(x, y, _MM_FROUND_TO_POS_INF | _MM_FROUND_NO_EXC);
  • _mm_add_round_sd(x, y, _MM_FROUND_TO_NEG_INF | _MM_FROUND_NO_EXC);
  • _mm_sub_round_sd(x, y, _MM_FROUND_TO_POS_INF | _MM_FROUND_NO_EXC);
  • _mm_sub_round_sd(x, y, _MM_FROUND_TO_NEG_INF | _MM_FROUND_NO_EXC);
  • _mm_mul_round_sd(x, y, _MM_FROUND_TO_POS_INF | _MM_FROUND_NO_EXC);
  • _mm_mul_round_sd(x, y, _MM_FROUND_TO_NEG_INF | _MM_FROUND_NO_EXC);
  • _mm_div_round_sd(x, y, _MM_FROUND_TO_POS_INF | _MM_FROUND_NO_EXC);
  • _mm_div_round_sd(x, y, _MM_FROUND_TO_NEG_INF | _MM_FROUND_NO_EXC);
  • _mm_sqrt_round_sd(x, x, _MM_FROUND_TO_POS_INF | _MM_FROUND_NO_EXC);
  • _mm_sqrt_round_sd(x, x, _MM_FROUND_TO_NEG_INF | _MM_FROUND_NO_EXC);
の命令を使えば、加減乗除と平方根の上向き丸めと下向き丸めを直接実行できます。

新しいコンパイルオプションを設け、-DKV_USE_AVX512と付けると、これらのAVX-512命令を使って区間演算を実行するようにしました。

core i9 7900X + ubuntu 16.04 + gcc 5.4.0 という環境で、examples以下にあるtest-nishi.ccというある非線形回路の全ての解を求める計算量の多いプログラムを使って、ベンチマークをしてみました。すると、
計算精度(端点の型)コンパイルオプション計算時間
double-O310.81 sec
double-O3 -DKV_USE_AVX512 -mavx512f5.55 sec
dd-O354.10 sec
dd-O3 -DKV_USE_AVX512 -mavx512f21.42 sec
のように高速化されました。なお、kvには他にもさまざまなコンパイルオプションがあり、今回改めてそれらをまとめたページ(丸めモードの変え方とコンパイルオプションまとめ)を作成しました。このページの末尾により詳細なベンチマークのデータがあります。

AVX-512は8個のデータを並列で処理する能力がありますが、今回のプログラムでは全く並列化していません。うまく並列処理するようにプログラムを書けば更に高速化できる可能性もあります。まだAVX-512が使えるPCは身近ではありませんが、一年も経てば役立つようになるのではないでしょうか。

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, y1, 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, y1, 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++以外の他の言語でも似たようなことは簡単に出来ると思います。

お役に立てば幸いです。

2019年4月10日追記

rectがバグっていたので直しました。

2017/08/23(水)kv-0.4.42

kvライブラリを0.4.42にアップデートしました。

今回は大きな変更はありません。
  • コンパイル時にマクロKV_USE_TPFMAを定義すると、twoproductの計算にfma命令を使うようになる。
  • dka.hpp (Durand Kerner Aberth法) の重解時の安定性が向上。
  • 精度保証付き数値計算とkvライブラリの概要を説明するスライドを掲載
くらいです。概要スライドは結構手間が掛かっていますので、読んで頂ければ幸いです。概要スライドの英語版も9割がた出来ているのですが、掲載は次回以降に。

2017/08/08(火)二重振り子の精度保証付き数値計算

少し前(5月頃)、二重振り子のシミュレーション動画がtwitterで流行したことがありました。最初のtweetがこれ。
わずかに異なる初期値をもった50本の二重振り子のシミュレーションを動画にしたもので、途中まで完全に重なっているように見える振り子が一気にバラける様子がとても面白い。

次は、それを三重振り子にしたもの。
gmpを用いて高精度計算をしており、ねとらぼで記事にもなりました。

どちらも常微分方程式の計算にはルンゲクッタ法を用いています。わずかな初期値のずれが後に大きな違いをもたらすことがとてもよく分かる動画ですが、一方で、ルンゲクッタ法で計算された値も真値とはわずかにずれており、当然その誤差も同様に後で大きな違いをもたらすことになります。だとすると、果たして意味のある計算になっているのかという疑問が生じます。

そこで、二重振り子の軌道を精度保証付きで計算し、ルンゲクッタ法とどのくらいずれるのか検証してみました。そのtweetがこれです。
このように、4次のルンゲクッタ法(刻み幅2^{-7})と精度保証付き計算とを比較してみると、t=8あたりから先は完全に違う軌道になってしまっていることが分かりました。

以下では、twitterに書けなかった細かい情報を備忘録も兼ねて書いておこうと思います。

使った式は、
\begin{align*} & (m_1+m_2)l_1\ddot{\theta_1} + m_2l_2\ddot{\theta_2}\cos(\theta_1-\theta_2) + m_2l_2\dot{\theta_2}^2\sin(\theta_1-\theta_2) +(m_1+m_2)g \sin \theta_1= 0 \\ & l_1l_2\ddot{\theta_1}\cos(\theta_1-\theta_2) + l_2^2\ddot{\theta_2} -l_1l_2\dot{\theta_1}^2\sin(\theta_1-\theta_2) + g l_2 \sin \theta_2= 0 \end{align*}
のようなものです(Wikipediaの記事と全く同じです)。舞台設定は
bbb.jpg

の通り。この式を\ddot{\theta_1}, \ddot{\theta_2}の2変数連立一次方程式と見て解いて正規形に直し、\dot{\theta_1}=\phi_1, \dot{\theta_2}=\phi_2のように置き直して1階4変数の常微分方程式とします。パラメータは
\begin{align*} m_1 &= m_2 = 1 \\ l_1 &= l_2 = 1 \\ g &= 9.8 \\ \end{align*}
とし、初期値は
\begin{align*} \theta_1(0) &= \frac{3}{4}\pi \\ \theta_2(0) &= \frac{3}{4}\pi \\ \dot{\theta_1}(0) &= 0 \\ \dot{\theta_2}(0) &= 0 \end{align*}
としました。

kvライブラリを用いて、この初期値問題を精度保証付きで計算するプログラムと、4次のルンゲクッタ法で計算するプログラムを書きました。両方のプログラムをzipで上げておきます ( doublependulum.zip )。4次のルンゲクッタ法の刻み幅は固定で2^{-7}としました。精度保証付きの方は全ての時刻の解を多項式の形で連続的に計算しているのですが、ルンゲクッタ法と同じ2^{-7}の刻み幅で密出力させています。この計算結果を動画にしたものが、先のtweetというわけです。

プログラムを走らせて得られた生データを上げておきます ( doublependulum-result.zip )。t=1のときの両プログラムのデータを抜き出すと、
\theta_1\theta_2
kv[ 0.14046540255551162,0.14046540255558421][ -0.71766989300731332,-0.71766989300724726]
rk40.1404652582264235-0.71766977132932186
となっていました。精度保証付きの方は全てのデータを区間で保持しており、この閉区間の中に真の解が存在することが数学的に保証されています。これを見ると、精度保証付き計算ではこの時点で13桁の精度を保っていますが、ルンゲクッタ法はすでに6桁程度しか精度がないことが分かります。例えば最初のtweetの動画では初期値に10^{-12}の摂動を与えていますが、t=1の時点ですでにルンゲクッタ法そのものの誤差の方が支配的になっているとするならば、本当に初期値の摂動の影響を計算していると言えるのでしょうか。

このデータをグラフにしてみました。
theta.png

t=8付近でずれが目に見えるようになり、そのあとは完全に異なる軌道を描いています。

精度保証付き計算もいつまでも高精度を保っていられるわけではなく、t=17付近を見るとわずかにグラフに幅があるのが分かります。この後一気に区間幅が爆発し、t=17.05付近で計算不能に陥ります。この精度保証付き計算プログラムはgmpのような高精度数を使用しているわけではなく、内部で使っているのは普通のdoubleです。試しに内部の数値型をdd(擬似4倍精度数)に変えてみたところ、t=30くらいまでは普通に破綻すること無く計算出来ました。

二重振り子の動画を見るだけなら、精度保証してもしなくても動きの「印象」はほとんど変わらないのですが、カオス系に対して数値シミュレーションで何らかの数学的な結論を得ようと思うならば、誤差が明確に把握できる精度保証付き数値計算は必須だと思います。みなさま、精度保証付き数値計算を使いましょう!

2017/05/07(日)だいたい国道2号自転車旅

たまには遊びの日記も書いてみよう。

このGW、9連休だったので、長い休みでないと出来ないことをしたいと思って、大阪発で西へ西へ自転車で向かってみました。
  • 長い休みはGWかお盆休みしかなくて、西の方はお盆休みは暑すぎて無理なので、GWに行くしかない。
  • 以前(11年前)に国道1号で大阪まで自転車で行ったことがあるので、その続き(国道2号)を一度走ってみたかった。
あたりが一応の動機でしょうか。自転車に乗るのは昨年8月のお盆以来なので、タイヤの空気を入れるところから始まります。大丈夫か俺。

4月29日(土)

夕方荷造りして自転車で東京駅へ。新幹線に載せて新大阪へ。夜10時頃新大阪着。

25.97km。

4月30日(日)

新大阪から少し迷いつつ、梅田の国道1号から2号に変わる交差点へ。ここから2号を西へ向かう。

IMG_5227.JPG
IMG_5229.JPG
IMG_5231.JPG
IMG_5232.JPG


体は重いが、だらだら走ってやがて神戸に。震災慰霊碑など。

IMG_5235.JPG
IMG_5236.JPG
IMG_5237.JPG
IMG_5238.JPG
IMG_5239.JPG
IMG_5240.JPG


明石着。明石と言えば東経135度。マンホールにも135って書いてあった。

IMG_5243.JPG
IMG_5247.JPG
IMG_5249.JPG


昼を食べながらこの日のゴールを考えた。ゴールは姫路、相生、赤穂温泉あたりが考えられたが、姫路では少し近すぎと思い、赤穂温泉に宿を予約した。が、どうもgooglemapの操作をミスっていたらしく、赤穂温泉は予想よりも20kmほど遠いことに走りながら気づく。こういう旅のときは基本的に景色を楽しみながら走りたいので夜間走行はしないポリシー。しかし、少し頑張らないと日没前に赤穂温泉に着くか怪しい。

時間が無いので姫路城は1分でスルー。

IMG_5253.JPG


赤穂市は国道2号を離れ国道250号へ。地形は山がちになり、標高100m程度のプチ峠を越え、何とか日没前に到着。

IMG_5256.JPG
IMG_5259.JPG


この日の走行距離は135.74km。体が慣れるまでは抑え気味で行こうと思っていたのに、少し走り過ぎた感。宿が思いの外いい宿で、温泉サイコーで、このままここに滞在したい気分に。

5月1日(月)

いい宿に後ろ髪をひかれつつ、出発。とりあえず市内を少し回った後、先へ向かう。

IMG_5266.JPG
IMG_5267.JPG
IMG_5268.JPG
IMG_5269.JPG
IMG_5270.JPG
IMG_5271.JPG
IMG_5272.JPG


軽く峠を越えて、岡山県に入る。

IMG_5273.JPG
IMG_5274.JPG


岡山市街地で見かけた。点字ブロック発祥の地だそうだ。

IMG_5286.JPG
IMG_5287.JPG
IMG_5288.JPG
IMG_5289.JPG
IMG_5290.JPG
IMG_5291.JPG


この日のゴール、倉敷では近すぎ、福山では少し遠いと思ったが、頑張って福山まで行くことにした。

国道2号の200kmポスト。

IMG_5295.JPG


福山市からは広島県。岡山県は一日で走り抜けてしまった。

IMG_5297.JPG


福山着。駅前のビジホの窓から福山城がとてもよく見えたのには驚いた。

IMG_5298.JPG
IMG_5300.JPG
IMG_5302.JPG
IMG_5303.JPG


この日の走行距離は122.45km。またも少し長め。この日の宿もとても快適だった。

5月2日(火)

朝もきれいな福山城を窓から見て出発。

IMG_5305.JPG


すぐに尾道市に入る。しまなみ海道は以前走ったことがあるのでスルー。

IMG_5309.JPG
IMG_5311.JPG
IMG_5312.JPG
IMG_5323.JPG
IMG_5324.JPG


原田知世の「時をかける少女」の撮影に使われた学校らしい。

IMG_5316.JPG
IMG_5317.JPG
IMG_5318.JPG
IMG_5319.JPG
IMG_5320.JPG
IMG_5321.JPG


出発して以来ずっと、国道2号走りづらいなあと感じていたところで、こんな看板を見て海沿いを行った方が楽しいだろうと予想、国道185号で呉方面に向かうことにする。

IMG_5329.JPG


国道185号は予想通りとても走りやすく、景色も最高。

IMG_5330.JPG
IMG_5331.JPG
IMG_5332.JPG


竹原市に入る。竹原には江戸時代の町並みを保存した地区があり、「時をかける少女」の大半は尾道ではなく竹原で撮影されたらしい。確かにこんな感じだったような気がする。

IMG_5347.JPG
IMG_5348.JPG
IMG_5349.JPG
IMG_5350.JPG
IMG_5351.JPG
IMG_5352.JPG
IMG_5353.JPG
IMG_5354.JPG
IMG_5355.JPG
IMG_5356.JPG
IMG_5357.JPG
IMG_5358.JPG
IMG_5359.JPG
IMG_5360.JPG
IMG_5362.JPG
IMG_5363.JPG
IMG_5364.JPG
IMG_5365.JPG
IMG_5366.JPG
IMG_5367.JPG
IMG_5368.JPG
IMG_5369.JPG
IMG_5370.JPG
IMG_5371.JPG
IMG_5372.JPG
IMG_5373.JPG
IMG_5374.JPG


最後は1700mもの長いトンネルを通って、呉に到着。

IMG_5379.JPG
IMG_5381.JPG
IMG_5383.JPG


そして呉と言えば、「この世界の片隅に」の舞台。駅ビルの中を探索してみたところ、あちこちで押してるのが分かる。

IMG_5385.JPG
IMG_5386.JPG
IMG_5393.JPG
IMG_5397.JPG
IMG_5399.JPG
IMG_5411.JPG
IMG_5412.JPG
IMG_5413.JPG
IMG_5414.JPG


この日の走行距離は109.70km。割と観光モードでした。脳内BGMはずっと原田知世の「時かけ」。

5月3日(水)

さて、1日2日が休みでなかった人にとってはこの日から本格的にGW。で、検索してみると、先へ進もうにも山口県内の宿が全く見つからない。仕方がないので今日は広島市街地までとし、疲労も溜まっているので休養も兼ねて観光モードに。

まずは「この世界の片隅に」聖地巡礼。

灰ヶ峰は街のどこからでも見える。かつてここに高射砲があった。

IMG_5445.JPG


小春橋。ただの橋である。

IMG_5446.JPG
IMG_5447.JPG
IMG_5448.JPG
IMG_5449.JPG


このへんも映画に出てきた気がする。

IMG_5454.JPG
IMG_5455.JPG
IMG_5456.JPG
IMG_5457.JPG
IMG_5458.JPG


歴史の見える丘。港を見下ろせ、戦艦大和を建造したというドックも見える。

IMG_5463.JPG
IMG_5464.JPG
IMG_5465.JPG
IMG_5466.JPG
IMG_5467.JPG
IMG_5468.JPG
IMG_5469.JPG
IMG_5474.JPG
IMG_5475.JPG


その歴史の見える丘の近く、映画中で最も悲しい場面はここで起きたという設定らしい。

IMG_5461.JPG
IMG_5476.JPG
IMG_5477.JPG


大和ミュージアムと海上自衛隊史料館。

IMG_5480.JPG
IMG_5481.JPG
IMG_5482.JPG
IMG_5484.JPG
IMG_5485.JPG


大和の甲板の1/4と同じ大きさの広場らしい。

IMG_5486.JPG
IMG_5489.JPG
IMG_5490.JPG
IMG_5492.JPG
IMG_5493.JPG
IMG_5495.JPG


映画に出てきた三ッ蔵。

IMG_5501.JPG
IMG_5502.JPG
IMG_5503.JPG
IMG_5504.JPG
IMG_5505.JPG


山の上に登って行って、主人公の家の最寄りのバス停「辰川」を目指す。

IMG_5506.JPG
IMG_5507.JPG
IMG_5508.JPG
IMG_5509.JPG
IMG_5510.JPG
IMG_5511.JPG
IMG_5512.JPG


今はバス停から港を見下ろすことはできない。

IMG_5515.JPG


そこで、更に上の住宅地を探索して、港を見下ろせる場所を探してみた。

IMG_5514.JPG
IMG_5523.JPG
IMG_5524.JPG
IMG_5525.JPG
IMG_5527.JPG


さて、呉観光はこのくらいにして、広島市街地へ向かうことにした。30kmほど。

IMG_5532.JPG
IMG_5533.JPG
IMG_5534.JPG
IMG_5535.JPG


広島城。

IMG_5536.JPG
IMG_5538.JPG


原爆ドーム。

IMG_5542.JPG
IMG_5543.JPG
IMG_5544.JPG
IMG_5550.JPG


相生橋。

IMG_5545.JPG
IMG_5546.JPG
IMG_5547.JPG
IMG_5548.JPG
IMG_5549.JPG


平和記念公園。

IMG_5551.JPG
IMG_5552.JPG
IMG_5553.JPG
IMG_5554.JPG
IMG_5555.JPG
IMG_5556.JPG


カープ戦のある日だけ提供されるという超絶カロリー食、ミートソースカツスパゲッティを食べた。

IMG_5564.JPG
IMG_5566.JPG


この日の走行距離は54.99km。完全観光モードの日でした。

5月4日(木)

前日宿が無くて広島停滞を余儀なくされたが、この日は検索してみると宇部に宿を発見。しかし、ここから宇部までは180kmもある。ここまでの実績を考えると日没前に180kmは厳しそうだが、仕方が無いので気持ちを切り替えて頑張ってみることにする。まとまった食事はとらず、2時間に一度計画的にコンビニでパンとコーラでカロリーを補給する作戦で。

岩国で山口県に入る。有名な錦帯橋を見る余裕なし。

IMG_5570.JPG


かなり距離は長くなるが、国道2号より快適に走れるであろう海沿いの国道188号を行くことにする。大変すばらしい道で、テンション上げて快走できた。昔よく走っていた頃の感覚が少し蘇ってきた感じ。野生解放(笑)。

IMG_5571.JPG
IMG_5573.JPG
IMG_5574.JPG
IMG_5575.JPG
IMG_5576.JPG
IMG_5577.JPG
IMG_5578.JPG


関東では全くみないけど西日本ではやたらとみる印象的な看板。

IMG_5582.JPG
IMG_5583.JPG
IMG_5584.JPG


いよいよゴールの北九州まで100kmを切った。

IMG_5588.JPG


国道190号へ分岐し、何とか宇部に到着。

IMG_5593.JPG
IMG_5594.JPG


この日の走行距離は実に189.20km。200kmオーバーは過去に何度かあるが、みんな夜間走行を含んでいるので、多分日没前にこれだけ走ったのは初めて。サイコンのaveも22km/hを超え、最近の中ではそれなりに速かった。走り出しが7:30、到着が18:30なのでちょうど11時間。いわゆるブルベの200kmは13時間半が制限時間と聞いているので、このくらいのペースなら楽に完走できる、ということか。途中のコンビニ休憩は10:00, 12:00, 14:00, 16:00と2時間おきだった。

5月5日(金)

最終日は距離が短いのでとても気楽。どんどん関門橋が近づいてくる。

IMG_5599.JPG
IMG_5601.JPG
IMG_5602.JPG
IMG_5603.JPG
IMG_5604.JPG
IMG_5605.JPG
IMG_5606.JPG
IMG_5608.JPG


自転車は橋は通れないので、人道トンネルで九州に渡る。

IMG_5609.JPG
IMG_5611.JPG
IMG_5612.JPG
IMG_5615.JPG
IMG_5618.JPG
IMG_5619.JPG
IMG_5620.JPG
IMG_5621.JPG
IMG_5622.JPG
IMG_5623.JPG
IMG_5625.JPG
IMG_5626.JPG
IMG_5627.JPG


国道2号終点の交差点へ。いつかこの続きの国道3号も走るのだろうか。

IMG_5631.JPG
IMG_5632.JPG
IMG_5633.JPG


小倉駅で自転車を畳んで新幹線に載せ、帰宅。

IMG_5639.JPG


ここまでの走行距離は57.54km。
東京駅から自宅まで自走。26.77km。

以上、天気に恵まれ(全く雨に遭わなかった)、充実した旅でした。走行距離は、新大阪-小倉間で669.62km、自宅と東京駅の間の往復も合計すると、722.36km。走ってるうちに少しずつ体力が戻ってくる感じは楽しかったけど、どうやって現実に帰ろうかのう。

(2020年3月31日追記)

ルートラボがサービス終了したので、地図の埋め込みをRide with GPSに頼ることにしました。
OK キャンセル 確認 その他