カテゴリー別アーカイブ: お勉強

1900年ぽい世界に異世界ワープしたら


(今回は常態で書きます。)

面白い記事を見つけた。

本の虫: 物理法則が同じかつ1900年ぐらいの科学レベルの異世界にタイムリープした現代人は現代の進んだ科学知識を活用して異世界チート主人公系ラノベのごとくになれるか

異世界チートものと呼ばれる作品群では、現実世界では平凡な主人公が魔法が使えたり妙に男女比が偏っていたりと現実とは設定の異なるパラレルワールドへ飛ばされ、そこでユニークな存在として活躍する様子が描かれる。

そこで、ワープ先の設定がほぼ現実世界と同じで、ただし1900年くらいの世界だったとして、その世界で主人公が現代の科学の知識を武器に活躍しようとしたらどうなるだろうか、という話である。面白そうなので自分なりに考察してみた。

「1900年」という時代

1900年前後という時代は、単に西暦で数えてキリが良いというだけはでなく、科学史にとっては特別な瞬間だ。これは、その直後に科学の景色そのものを塗り替えてしまった2つの物理学上の偉大な進歩がなされた時代だからだ。となると異世界主人公としてはかなり都合の良い時代とも言えるだろう。

一方、科学的知識をもとに実業家として活躍することはできるだろうか。この時期、工学分野は順調に発展を続けているように見える。1900年といえば第5回パリ万国博覧会が開かれた年で、飛行船ツェッペリン号の初飛行の年でもある。例えば映画「スチームボーイ」の世界観がこれに当たるだろうか。エジソンの有名な発明品はもうだいたい登場していて、産業のコメと呼ばれた鉄も量産に向けて邁進している。既に大量の技術者・専門家が活躍している時代で、この時代に異世界主人公が活躍しようとするなら分野を少々考えなければいけない。

いずれにしても、知識の利用に現代の技術が必須なものでは駄目だろう。その当時の世界に役に立つものを用意しなければ異世界チートにはならない。

区分けが現代的な気もするが、分野ごとにわけて考えてみよう。主人公のスペックとしては駆け出し科学者くらいを想定している。調べれば大量に出てきそうなので、自分で思いついたものを書いていくことにする。(年号は調べた。)

理学

先ほどの2つの偉大な進歩について考えてみる。

続きを読む


[#プリクラ問題]に対する貪欲解


別にプリクラに貪欲なわけではありません。

こちらの問題にまつわる話です。

下限については比較的よいものがすぐ求まるのですが、理論解となると私の知識では太刀打ち出来なさそうだと思いました。

困ったときにはコンピュータ。理論解ではありませんが、それらしい方針を与えて近似解を求めてもらおうという魂胆です。

ソースコードは一番下へどうぞ。正直な話、今回はソースをはりつけるために作られた記事です。

続きを読む


何票まで開票したら当選確実といえるのか


招き猫

招き猫

 

選挙シーズンですね。

選挙といえばお馴染みのテレビの開票速報。20時を過ぎた瞬間から「ほんとに確実なのかよ、というか1票でも確認したのかよ」と言いたくなるペースで大物政治家の当確報道が流れていくのが恒例行事です。

まあそう言って報道につっかかるのも野暮というもので、単に確実という言葉が100%でなくだいたい正しいという意味で使われているだけですね。(個人的には票を見ずに結果を宣言するのはなんとなく投票という行為自体を軽く扱っている気がするのですが、それはまた別の話。)

結局のところ、どういう理屈で当確が出ているのかは視聴者にはわからずじまいなわけです。出口調査や前回までの結果など複雑なノウハウが詰まったものだし簡単に教えてくれるものではないでしょうが、このご時世、「なんかいろいろやってるんだよ、まあ信じておきな」ではどうにもしっくりこないところがあります。

そこで今回は視点を変えて、「逆に票だけを見て確率的に『確実』というためには、全体のうちのはじめの何票くらいを集計すればいいのか」というのを試しに計算してみたいと思います。

やっていることは二項分布のベイズ推定をしているのみで、数学的にはゆったりした話かと思います。

(以下、なるべく政治的な話にならないよう、具体例では実名を挙げないように気をつけます。)

続きを読む


[作ってみた]物理ぷよぷよ


私のいる学科の演習のひとつに、「シミュレーション&ビジュアライゼーション演習」というものがあります。
シミュレーション&ビジュアライゼーション演習

簡易的ですが実際に動作を見れるシミュレータを題材に各人があれこれいじることで、いわゆる物理シミュレーションに関して比較的楽しく学ぼうという趣旨の授業です。

DA099_L

本来は演習用のソフトですが、せっかくソースコードも公開されているので、気晴らしにひとつ作ってみました。テーマは「物理演算+ぷよぷよ」です。ソースと実行ファイルは一番下に。

今回はぷよぷよといっても対戦要素は無しです。元のルールに物理演算(?)を組み込むことによる大惨事を見届けようというのが今回の趣旨になります。

続きを読む


拍と拍子の概念を宇宙人に伝えよう


先日、こんなツイートを見かけました。

“数学と物理がむちゃくちゃ出来るのだが音楽的センスが全く無いと言う後輩に、拍と拍子の概念を伝えるにはこれでいいのか?”

“先日ツイートした「拍と拍子の概念を数学で表現する」の検証を行いました。 一応計算はできたのですが、拍子の概念を定義するのは非常に難しいということが分かりました。”


「音楽的センスが皆無だが数学と物理なら話が通じる。拍と拍子の概念をどう伝えればよいか?」

ふむ。早い話が宇宙人を想定すればいいのですね。
(私の脳内宇宙人はいつも死ぬほど数学と物理に長けている設定になっています。)

先のツイート内では拍子テンプレート関数なるものを用意していました。これなら拍子以外の要素(コード進行とか?)も考えられそうですが、あまり複雑にすると楽曲のテンポに対しては結果が安定しなくなりそうです。

私ならどうするか。拍子だけならフーリエ変換でもいけそうです。
音をエネルギーか何かの時系列データで表現して、それをフーリエ変換して波数空間で見てあげれば、拍や拍子のピークが見えてもよさそうです。拍の半分の周波数でピークが出れば2拍子、1/3倍の周波数でピークが出れば3拍子です。

と、予想を書いてみるのは簡単ですが、本当にこんなことになるのでしょうか。

というわけで試しにやってみました。
関連文献をあたれば何かしら見つかりそうですが…

続きを読む


マンデルブロー集合へ飛び込む


先日のマンデルブロー集合の10の200乗倍で作成したプログラムがあるので、今回はマンデルブロー集合の様々な地点へ飛び込んだ動画を作ってみました。

前回の記事を見ていない方はそちらからどうぞ:

[フラクタル] マンデルブロー集合 10の200乗倍への挑戦

今回訪れる地点は計5箇所です。どの点も拡大していく最中に集合全体と同じ形が現れます。フラクタルらしくて面白いと思います。

具体的には以下の点です。緑は前回の場所。それぞれ個性的な場所だと思います。

Mandelbrot Set with Circles (Picture from Wikimedia)

Mandelbrot Set with Circles (from Wikimedia)

この集合はフラクタルなので自己相似の図形ではあるのですが、ただ単純にどこでも同じ形を持っているわけではありません。拡大する場所を変えると景色が一変してしまいます。

肝心の動画はこちら。ニコニコ動画はこちら

続きを読む


現れては議論になるあの確率の問題を愚直にやってみた


今回の話題はこれです。

ジョーカーを除いたトランプ52枚の中から1枚のカードを抜き出し、
表を見ないで箱の中にしまった。
そして、残りのカードをよく切ってから3枚抜き出したところ、
3枚ともダイヤであった。
このとき、箱の中のカードがダイヤである確率はいくらか。
答えが1/4ってのは納得出来ない!
10/49だろ!!

定期的にあちこちで話題になる確率の問題です。

調べると2chまとめサイトなどでも話題にされていますし、考察しているブログなども多数見つかります。
Yahoo!知恵袋では早稲田大学の過去問だという方がいました。

論調もいろいろで、1/4だよ派と10/49だよ派だけに限らず、本文の解釈の仕方で違うだの、この問題は単純な確率の問題ではないだのと様々な意見が出されています。

実際には数学的にきちんと議論できて正解も出せるのですが、それでは納得できない人は大勢います。数式で考えるというのは抽象化のレベルが相当高いので、計算上はともかく問題文を本当に反映できているのか?という疑問が起きるのは不思議なことではありません。

あれこれ議論する前に、実際にやってみればいいんですね。

今回はその結果の話と、結果の「正しさ」の扱いについて少し書いておきます。

続きを読む


「抜き打ちテストのパラドックス」を撃破する


「抜き打ちテストのパラドックス」という有名なパラドックスがあるのですが、たいていどの記事でもパラドックスの紹介だけで終わっていて、もやもやするのでこの記事を書きました。
自分の中ではパラドックスのありかがわかっているつもりなのですが、同意見の主張が見つからないので多少気になっているところでもあります。

ドロステ効果

Taken by fdecomite, Flickr


パラドックスの概要

抜き打ちテストのパラドックスというのは、以下のようなものです。

「とある学校、とあるクラスの話。あるとき先生が以下のように言いました。

『来週の月曜から金曜の間に、抜き打ちテストを行います。ただし、君たちはこの抜き打ちテストがいつ行われるか予測できない。』

続きを読む


[絶対に儲かる賭け?] サンクトペテルブルグのパラドックスを律儀に試す


「サンクトペテルブルグのパラドックス」は、現実的にはあまり儲からなそうな賭けの期待値が、計算上無限大に発散するとされる有名なパラドックスです。

パラドックスを知らない人のために賭けの要点を書いておくと、以下のようになります。

「コインを投げ続け、表が出たらゲーム終了。表が出たのが1回目なら1円獲得、2回目なら2円、3回目なら4円…というように、裏が1回出るたびにもらえる金額が倍になる。いくらの賭け金ならこの賭けに参加するべきか。」

儲け額の期待値Wは以下のように計算されます。1/2で1円、1/4で2円、となるので…
  W=\sum_{k=0}^{\infty}\left(2^{k-1}\frac{1}{2^k}\right) = \frac{1}{2}+\frac{1}{2}+\frac{1}{2}+\dots = \infty

ここまで見てだまされないぞと思った人は、しっかり悩んでみてください。

Coins

Taken by xJason.Rogersx, Flicker.

先日友人との間で話題になって、実際のところおおよそどんな具合で儲かるのかやってみようということでさくっと書いてみたものです。確率についてなんやかんやと議論するのも楽しいですが、ゴリ押しでデータを集めるのもまた面白いものがあります。あと探しても意外とそういう資料が見当たらなかったので。


ソースコード

いつもどおりC++です。C++11の機能を使っていますので、例えばgccとかでコンパイルしたい場合は -std=++11 とか -std=c++0x とかのオプションをつけてください。

#include <ctime>
#include <iostream>
#include <random>
#include <functional>

int main(int argc, char** argv){
  if(argc != 2){
    std::cerr << "Wrong argument!" << std::endl;
    return -1;
  }
  long long int loopNum = 1;
  for(int i = 0; i < atoi(argv[1]); i++){
    loopNum *= 2;
  }
  std::cout << "Loop: " << loopNum << std::endl;

  time_t timer;
  time(&timer);
  auto rand = std::bind(std::uniform_int_distribution(0,1),
      std::mt19937(static_cast<unsigned int>(timer)));
  long long int total = 0;
  for(long long int trial = 0; trial < loopNum; trial++){
    long long int earn = 1;
    while(rand()){
      earn*= 2;
    }
    total += earn;
  }
  std::cout << "Earned: "
    << static_cast<double>(total)/static_cast<double>(loopNum)
    << std::endl;
  return 0;
}

特記するようなものもありません。コマンドライン引数から2の何乗回計算するかを決めて、律儀に試行を繰り返して平均獲得金額を求めています。

カウントがlong long int型なので2の50乗回程度までは問題ないと思いますが、そのはるか手前の2の30乗前後(数億程度)で計算時間がきつくなってくると思います。
まあ乱数も毎回1ビットしか使っていないしシングルスレッドだしで、高速化する余地はまだ残っています。


結果

大量に試行した結果は以下のようになります。あくまで愚直に。力強く。

セントペテルスブルグの賭け

横が1セットあたりの試行回数、縦が1回あたりの平均獲得金額です。両方logスケールなのに注意。
(表が出るまでを1回の賭けとし、1セットでn回行うと定義しています。平均獲得金額は儲け額をnで割った値。)

不思議なグラフです。まず、回数を増やすと平均獲得金額は順調に上がっていくことがわかります。1,000,000,000回以上やるつもりなら、1回あたり10円以上儲かることがほぼ確実になります。このままグラフを右に伸ばしていったらどうなるでしょう。

もうひとつ面白いことがあって、それはばらつきが減らないことです。例えば右上にある点、だいたい10,000,000回で平均獲得金額が10,000円を超えていますが、つまりこの1セットで単純にトータル一千億円以上儲けているということです。とんでもない大当たりです。

胴元と挑戦者、どちらの席につきたいですか?


今回は横軸がlogスケールで均等になるようにサンプリングしています。離散的な値しかとらないので左側はすかすかに見えますが、実際は点が重なっているだけです。
また、左上から右下へのぼんやりとした斜線が大量に見られます。これは「1回大当たり」を成し遂げたときの線になっています。回数を増やすと同じ大当たりでも「1回あたりの獲得金額」は減っていくので右下がりになるという具合です。その代わり「もっとすごい大当たり」をひく確率も出てきます。

モンテカルロ法などでは試行回数を増やすと値が落ち着くのですが、この問題はおもしろいことにいくら試行回数を増やしてもばらつきが収まらないようになっています。よくできています。

儲け額の分散も出してみようと一度書きかけたのですが、ちょっと考えてからこの問題に対して無駄な試みであることがわかりました。

コンピュータはしばしば想像力を働かせる道具として役に立つのですが、今回のように神通力の効かないヤバそうな問題ではちょっとわけが違ってきます。こんな問題でも扱えてしまう数学の力は偉大です。

結果の意味とこの賭けの本当の期待値についてはいろいろ考えてみて下さい。


運動方程式の数値計算と速度ベルレ法の威力 (後半)


前半の補足です。前半で紹介したシミュレータ云々について。

particle1


この粒子の振る舞いについて

「なんで星の運動を模してるのに楕円軌道じゃないんだ?」と思った方はとても勘のいい方です。

これは2次元の運動をどう考えるかによるものです。
3次元空間の平面上に粒子を配置させて運動させた場合、重力は距離の2乗に反比例しますが、
3次元空間に無限長の棒を(平行に)置いて運動させた場合、重力は距離の1乗に反比例します。

これはまあ真面目に積分してもそうなりますし、電磁気学を勉強したことのある方は雰囲気で理解できるかと思います。後者は宇宙空間では仮定が不自然すぎるだろうと思われるかもしれませんが、万有引力が3次元空間で逆2乗の法則に従っているとするなら、2次元では逆1乗になるのが自然だとも考えられます。

まあ、今回はそこまで哲学的な話ではなく、単に逆1乗のほうが見栄えが良かったからです。

どう見栄えに影響するかですが、これは無限遠点での挙動の違いを考えればだいたいわかります。位置エネルギーは力の積分(の-1倍)で与えられますが、これより力が逆2乗、逆1乗の場合のそれぞれについて位置エネルギーは逆1乗、logとなります。つまり前者ならエネルギーが無限遠である値に収束するのに対して、逆1乗なら際限なく増えていきます。

つまり、逆2乗ではひとたび粒子が第三宇宙速度に達してしまうと二度と戻ってこないのに対し、逆1乗なら少なくともいずれは引き戻されてくるという大きな違いがあります。見た目に粒子がどこかに飛んでいってしまうのは寂しいですね。

よく「この世が2次元だったらいいのに」という言葉を聞きますが、そんなことをしたら、上記の理由から世界中のものがある一点に集まってしまってつまらない世界になるだろうなあと思っています。

なお、粒子があまり近づきすぎると計算上の問題からヤバいことが起きるので、引力を打ち消すだけの反発力を与えてあります。
現実だと衝突しますが、無理にエネルギーを保存させようとして星が完全弾性衝突をするのもあまりリアリティがないですね。


以前の3重振り子を使わなかった理由

これは速度ベルレ法の適用範囲に限界があるからです。(下は剛体3重振り子の動画)

シンプレクティック法は、影のハミルトニアンと呼ばれる量を厳密に保存する運動を記述します。この際、位置q_{t+1}を計算する際にq_{t+1}のときのハミルトニアン(の偏微分)が必要になります。これは普通に考えると右辺と左辺がからまってさくっと解けません。
しかし都合のいいことに、単純な場合ハミルトニアンHは位置qと運動量pの足し算に分離でき、このときハミルトニアンの偏微分の形からqがなくなります。その場合の解法のひとつが速度ベルレ法となるわけです。

以前の3重振り子では、ハミルトニアン(というよりラグランジアン)の計算に3つのθという一般化座標を使っていました。このためハミルトニアンの分離が行えなくなり、(実際にはできるかもしれないけど私はその数学を持ち合わせていないので、)速度ベルレ法をはじめとした陽解法が使えない状態に陥っていました。

こういった場合には陰解法のガウス・ルジャンドル法などを使って解くことができます。

なんとなく当然のような気もしますが、よくよく考えてみるとこの仮定は結構不思議なことです。分子動力学法などではたいていこの仮定が成り立ってしまいます。


ソースコード

ちょっとだけかいつまんで書いておきます。C++です。

まずは系の状態の定義。数値積分に関しては位置と速度のペアのリストさえあればいいのだけど、肝心な力の計算ではどの値がどの物理量に対応しているかを知らなければならず、もどかしい。

かといって値を参照して保持しておくほどの大げさなものでもない。そこで、どうせ数値積分では四則演算くらいしか使わないので、粒子のvectorに必要な演算子のみ定義した構造体を持っておきます。

	struct partState{
		double x, y;
		double vx, vy;
		partState()	: x(0.0), y(0.0), vx(0.0), vy(0.0){}
		partState(double _x, double _y, double _vx, double _vy)
			: x(_x), y(_y), vx(_vx), vy(_vy)
		{}
	};
	struct partSystem{
		std::vector<partState> pss;
		partSystem(int size){
			pss = std::vector<partState>(size);
		}
	};
	const partState operator+(const partState& lhs, const partState& rhs);
	const partState operator-(const partState& lhs, const partState& rhs);
	const partSystem operator+(const partSystem& lhs, const partSystem& rhs);
	const partSystem operator-(const partSystem& lhs, const partSystem& rhs);
	const partState operator*(const double lhs, const partState& rhs);
	const partSystem operator*(const double lhs, const partSystem& rhs);

力の計算はもうほんとに全ペアで単純に逆1乗で計算してるだけで、高速化のかけらもありません。
ガチで分子動力学法などをやるときには今回のO(n^2)のようにならない工夫が必要です。

		partSystem calcForce(const partSystem& _ps){
			partSystem ans(size);
			for(int i = 0; i < size; i++){
				for(int j = i+1; j < size; j++){
					double dx = _ps.pss.at(i).x - _ps.pss.at(j).x, dy = _ps.pss.at(i).y - _ps.pss.at(j).y;
					double r2 = dx*dx + dy*dy;
					double r = std::sqrt(r2);
					if(r > r_threshold){
						//double r12_e3 = r2/0.0001;
						//r12_e3 = r12_e3*r12_e3*r12_e3*r12_e3*r12_e3*r12_e3;
						double f = -g*masses.at(i)*masses.at(j)/r;// + g2*masses.at(i)*masses.at(j)/r12_e3;
						double fx = f*dx/r, fy = f*dy/r;
						ans.pss.at(i).vx += fx;
						ans.pss.at(i).vy += fy;
						ans.pss.at(j).vx -= fx;
						ans.pss.at(j).vy -= fy;
					}
				}
				ans.pss.at(i).x = _ps.pss.at(i).vx;
				ans.pss.at(i).y = _ps.pss.at(i).vy;
			}
			return ans;
		}

反発力を与える際、分子動力学チックに高次の多項式の反発力を与えようとした名残があります。結局そこまでする必要もなく削除。かわりに距離にしきい値を導入します。
エネルギーの計算(表示用)でも不整合がないようにしておく必要があります。

あとは今回のテーマだった数値積分。ソースはとっても単純。

		void move_euler(const double dt){
			auto psDiff = calcAccel(ps);
			ps = ps + dt * psDiff;
		}
		void move_heun(const double dt){
			auto k1 = calcAccel(ps);
			auto k2 = calcAccel(ps + dt*k1);
			ps = ps + dt/2.0*(k1+k2);
		}
		void move_runge_kutta(const double dt){
			auto k1 = calcAccel(ps);
			auto k2 = calcAccel(ps + dt/2.0*k1);
			auto k3 = calcAccel(ps + dt/2.0*k2);
			auto k4 = calcAccel(ps + dt*k3);
			ps = ps + dt/6.0*(k1+2*k2+2*k3+k4);
		}
		void move_velocity_velret(const double dt){
			auto k1 = calcAccel(ps);
			auto p2 = ps;
			for(int i = 0; i < size; i++){
				p2.pss.at(i).x += dt*ps.pss.at(i).vx + dt*dt/2.0*k1.pss.at(i).vx;
				p2.pss.at(i).y += dt*ps.pss.at(i).vy + dt*dt/2.0*k1.pss.at(i).vy;
			}
			auto k2 = calcAccel(p2);
			for(int i = 0; i < size; i++){
				p2.pss.at(i).vx += dt/2.0*(k2.pss.at(i).vx + k1.pss.at(i).vx);
				p2.pss.at(i).vy += dt/2.0*(k2.pss.at(i).vy + k1.pss.at(i).vy);
			}
			ps = p2;
		}

速度ベルレ法だけ、位置と速度を明示的に扱っているのがわかります。