コンソール行列計算ソフト sf により光学系における光線追跡が可能です。 ただし sf は光線追跡専用のソフトではありません。行列数値計算ソフトです。光線追跡のためには sf 以外に、エディタと gnuplot と python を組み合わせた、コンソールから操作しながら計算していきます。
このように sf での光線追跡ではユーザーにコンピュータの使いこなしについて一定の知識を要求します。エディタ や gnuplot の名前ぐらいは聞いたことがある方、または新しいことに挑戦する方でないと使えないと思います。マウスだけで操作できる素人向けの統合環境ではありません。
逆に 行列数学/コンピュータについて理解・知識のある方ならば、短時間で強力な汎用的で応用の利く強力なツールを入手できます。sf 数値計算に、gnuplot はデータの可視化に特化したソフトであり、特化した分習得が容易で他への応用ができます。python は言語であり、この全体的な習得には手間がかかります。でも球面レンズの計算に限定すればブラック・ボックスにできます。 チャレンジ願います。
なお、sf は光線追跡のためのソフトではありません。汎用的に数値計算するソフトです。これに 光線追跡のための python script を 70 行程度書いただけで光線追跡ができてしまう所が自慢です。
御自分でも数値計算しながら以下を読んでください。そのほうが楽しめます。sf を使えば計算の手間はかかりません。(Windows が動く HDD に専用のディレクトリを掘って、こちらから sf20beta をダウンロードして解凍するだけで以下の計算ができてしまいます。インストーラなどありません。レジストリを汚すことはありません。不要になったら、ディレクトリごと消すだけです。"sf expression" が左上に書いてあるブロックは全て sf で計算可能です。sf 式をコピーし、コンソールで sf "コピーした sf 式" を実行させるだけで実行されます。コピーした sf 式は自由に修正できます。数値実験できます。是非ご自分の手でも数値実験を行いながら読んでみて下さい。
光線追跡で使う sf, gnuplot, python それぞれについて概要を説明します。実際に使っている機能は、それぞれの機能の一部だけです。簡単です。
sf.exe は行列計算をさせるコマンドライン・プログラムです。計算結果を test.val などの拡張子が .val のテキスト・ファイルに残します。この test.val に対して変数と見なして sf "test2 = 3*test^2 + test^-2 + ~userFunction.py(test/3)" などと計算させられるのが sf の便利なところです。test.val を行列やベクタ・データにもできるので多くの強力な数値計算ができます。
新しい sf2.0 の機能を使います。また sf の機能の詳細ははこちらを参照ください。
sf は線形行列演算に特化したソフトです。非線形演算など sf.exe だけでは対応しきれない処理 userFunction を作って sf から呼び出して対処します。今回の ray trace では ray.py python script にレンズ面での ray trace の位置と方向を計算させます。
gnuplot は GPL で配布されている可視化ソフトです。ビジネスでのプレゼンテーションに使うには専用の何十万もするソフトより劣ります。自分で技術検討するための可視化ソフトと考えるならば、十分とも言える機能を持っています。フィルタ・スクリプトで扱いやすいのも嬉しいところです。
gnuplot をインストールするには ftp://ftp.gnuplot.info/pub/gnuplot/ から gp400win32.zip をダウンロードします。gp400win32.zip の中から bin directory にある wgnuplot.exeと wgnuplot.hlp を path の通ったディレクトリに解凍するだけです。とりあえず試すたけならば、カレント・ディレクトリでかまいません。とくにインストーラーは必要ありません。
gnuplot の操作は専用の gnuplot terminal window からコマンド操作を行うのが普通ですが、この光線追跡では、それは行いません。gnuplot は開始するときに gnuplot.ini ファイルを探し、そこに plot コマンドがあれば実行する機能があります。その gnuplot.ini には表示データも記述できます。この光線追跡ではコンソールに出力される sf の計算結果を copy and paste で gnuplot.ini に写し gnuplot が扱えるフォーマットにエディタで編集します。
その意味で光線追跡で gnuplot に行わせる具体的な操作は start wgnuplot.exe をコンソールから行わせることだけです。ray trace 表示コマンドと表示データは、あらかじめエディタでテキスト・データとして作ります。この gnuplot.ini データの作り方は、実例のところで詳細に説明します。
光線追跡で使うのは二十行程度のレンズ面での光線の位置と方向を計算する ray.py スクリプトだけです。球面レンズの光線追跡を行うだけならば ray.py を black box としてしておき、何もする必要ありません。自分で非球面レンズを使うなど拡張したいときに ray.py の中を一部修正することになります。そのときのために、もう少し python について説明します。
python は高機能なスクリプト言語です。これを使いこなせれば graphic 表示、web server 制御 windows アプリケーションなど殆んどのプログラムが作れます。それらを簡単に記述するためのパッケージが全世界の開発者たちが作り改良しつづけています。しかも無償で配布されています。
数学系の計算機能モジュールとしては scipy が配布されています。fft/特殊関数/Matlab 互換プロットパッケージ/統計処理パッケージ/積分/常微分方程式ソルバー/Lapack 線形演算 などが入っています。 。とりあえず行列処理をするだけならば、http://www.nasuinfo.or.jp/FreeSpace/kenji/sf/fastTour/pyLinear.htmここを見れば行列の加減乗除算や固有ベクタ/固有値を求めることはできるようになるはずです。インストールについても説明してあります。
光線追跡で使うのはベクトル演算機能と fsolve(.):Non-linear multi-variable equation solver. の二つだけです。この二つについて理解するだけで、凹面鏡/非球面レンズ光学系など多くの光学系での光線追跡もできるように python script:ray.py を拡張できます。最後に説明する ray.py スクリプトのソースも読んで見てもください。
一つの点光源と一つの球面凸レンズになる下のような光学系集光の様子を Snell の法則によって計算します。
r= 30mm r=-30mm (-10mm,15mm) (20mm,0), (25mm,0) (80mm,0) │ │ ─────── │──│\ \ │ │ \ \ │ │ \ \│ │ \ │──│─────────── │ │ \ 屈折率 1.5
gnuplot に ray trace を描かせるとき、レンズも書いておくためのデータを作成します。直接には光線追跡の計算結果には影響しません
下の sf 式により (20mm,0) の位置に半径 30mm の球面の置いたとき、その表面の <x,y> 座標位置を計算させます。
N=32,<<-`π/2,N,`π/N @θ| <30`mm+20`mm,0>-30`mm >> sf "N=32,<<-`π/2,N,`π/N @θ| <30`mm+20`mm,0>-30`mm >>" loop count:_n 0 loop count:_n 1 ・ loop count:_n 31 [[ 32, 2 ]] < 0.05, 0.03 > < 0.0470595, 0.0298555 > ・ < 0.0235424, 0.0141419 > < 0.0222836, 0.0114805 > < 0.0212918, 0.00870854 > < 0.0205764, 0.00585271 > < 0.0201445, 0.00294051 > < 0.02, 0 > < 0.0201445, -0.00294051 > < 0.0205764, -0.00585271 > < 0.0212918, -0.00870854 > < 0.0222836, -0.0114805 > < 0.0235424, -0.0141419 > ・ < 0.0470595, -0.0298555 >
この sf 式で行わせているのは [-π/2, π/2] の π の角度を N=32 等分し、それぞれの角度を θ にいれて <30`mm+20`mm,0>-30`mm <!cos(θ),!sin(θ)> を N 回計算させていることです。
`π==3.142 は予め sf.ini に宣言されています。θ のようにギリシャ文字を変数名に使えます。これは数式の可読性を高めてくれます。
`mm のように MKSA 物理単位を sf 式の中に書けるのが自慢です。やっているのは `mm=0.01 の値を入れてある変数を用意しておくだけなのですが。物理単位を数式中に入れるだけでドキュメント性が高まります。数式が何を意味しているのか良く分ります。
計算結果はコンソールに表示されます
同様に (25mm,0) の位置に半径 -30mm の球面の置いたとき、その表面の <x,y> 座標位置を計算させます。
N=32,<<-`π/2,N,`π/N @θ| <-30`mm+25`mm,0>+30`mm >> sf "N=32,<<-`π/2,N,`π/N @θ| <-30`mm+25`mm,0>+30`mm >>" loop count:_n 0 loop count:_n 1 ・ loop count:_n 31 [[ 32, 2 ]] < -0.005, -0.03 > < -0.00205949, -0.0298555 > ・ < 0.0214576, -0.0141419 > < 0.0227164, -0.0114805 > < 0.0237082, -0.00870854 > < 0.0244236, -0.00585271 > < 0.0248555, -0.00294051 > < 0.025, 0 > < 0.0248555, 0.00294051 > < 0.0244236, 0.00585271 > < 0.0237082, 0.00870854 > < 0.0227164, 0.0114805 > < 0.0214576, 0.0141419 > ・ < -0.00205949, 0.0298555 >
コンソールに出力されている上のレンズ球面位置データより、レンズとして使える部分の数値の組だけをエディタで copy and paste して下のように gnuplot ini の名前のテキスト・ファイルをカレント・ディレクトリに作ります。
type gnuplot.ini plot "-" using 1:2 with line 0.0222836 0.0114805 # レンズ面左側 0.0212918 0.00870854 0.0205764 0.00585271 0.0201445 0.00294051 0.02 0 0.0201445 -0.00294051 0.0205764 -0.00585271 0.0212918 -0.00870854 0.0222836 -0.0114805 0.0227164, -0.0114805 > レンズ面右側 0.0237082, -0.00870854 > 0.0244236, -0.00585271 > 0.0248555, -0.00294051 > 0.025, 0 > 0.0248555, 0.00294051 > 0.0244236, 0.00585271 > 0.0237082, 0.00870854 > 0.0227164, 0.0114805 >
「plot "-" using 1:2 with line」は gnuplot を制御するコマンドです。
上の gnuplot.ini をエディタでカレント・ディレクトリに作っておいて wgnuplot.exe を走らせると下のようにデータを可視化してくれます。
start wgnuplot
後で ray trace データを作り、この gnuplot.ini の下に追加してやることで光線追跡の計算結果を可視化します。
点光源レンズ面に向けて発射する 10 本の光線を下の sf 式を使って rayLineMt.val ファイル変数に保存します
N=10, rayLineMt = <<0,N,(0.6`π/2)/(3N) @θ|<-50`mm,10`mm, !cos(-θ),!sin(-θ)> >> sf "N=10, rayLineMt = <<0,N,(0.6`π/2)/(3N) @θ|<-50`mm,10`mm, !cos(-θ),!sin(-θ)> >>" loop count:_n 0 loop count:_n 1 ・ loop count:_n 9 [[ 10, 4 ]] < -0.05, 0.01, 1, 0 > < -0.05, 0.01, 0.999507, -0.0314108 > < -0.05, 0.01, 0.998027, -0.0627905 > < -0.05, 0.01, 0.995562, -0.0941083 > < -0.05, 0.01, 0.992115, -0.125333 > < -0.05, 0.01, 0.987688, -0.156434 > < -0.05, 0.01, 0.982287, -0.187381 > < -0.05, 0.01, 0.975917, -0.218143 > < -0.05, 0.01, 0.968583, -0.24869 > < -0.05, 0.01, 0.960294, -0.278991 >
(-0.05,0.01) の点から (1,0), (0.999507, -0.0314108), .. の方向に射出される 10 個の光線を 10x4 rayLineMt 行列変数としてカレント・ディレクトリに作ります。
光線追跡を x,y の位置と光線の角度 θ で表現する方法も考えられます。このほうが単位ベクトルの二つの数値の組より少ないデータで済みます。でも角度 θ を使うと、レンズ面との交点を計算するときなどに sin(θ) などの三角関数が数多く出てきます。数式が複雑になります。単位ベクトルを使っておいたほうが外積などであらわされるベクトル空間の性質を使った数式で処理させられます。python はベクタを扱えるので、角度θよりも単位方向ベクタを使ったほうが単純な数式で ray trace program を記述できます。
なお、光線追跡のデータを可視化するためには(位置, 方向単位ベクトル)行列を複数扱わねばなりません。そのために sf アレー変数 cntnr を作ります。まず rayLineMt を cntnr に追加します。
cntnr=<{}> sf "cntnr=<{}>" % BaseArrayDict 0 cntnr{}=rayLineMt sf "cntnr{}=rayLineMt" [[ 10, 4 ]] < -0.05, 0.01, 1, 0 > < -0.05, 0.01, 0.999507, -0.0314108 > < -0.05, 0.01, 0.998027, -0.0627905 > < -0.05, 0.01, 0.995562, -0.0941083 > < -0.05, 0.01, 0.992115, -0.125333 > < -0.05, 0.01, 0.987688, -0.156434 > < -0.05, 0.01, 0.982287, -0.187381 > < -0.05, 0.01, 0.975917, -0.218143 > < -0.05, 0.01, 0.968583, -0.24869 > < -0.05, 0.01, 0.960294, -0.278991 >
点光源から射出された光線が最初にレンズ面に到達したときの位置を計算で求められます。レンズの位置と形状が分れば、計算で光線がレンズ面とクロスする点が求まります。またクロスする点がまとまれば、その点における法線とレンズの屈折率より、レンズ側での光の方向が計算できます。
即ちレンズ位置 (20mm,0) とレンズ半径 30mm と屈折率 1.5 と rayLineMt が与えられば、左側のレンズ面に入り込んだ直後の位置と光線の単位方向ベクトルが定まります。その計算を sf で行うのは複雑になりすぎるので python の fsolve 機能を使って計算させます。下の sf 式により計算させます。
cntnr{} = ~ray.py(20`mm,30`mm, 1.5, rayLineMt) sf "cntnr{} = ~ray.py(20`mm,30`mm, 1.5, rayLineMt)" [[ 10, 4 ]] < 0.0217157, 0.01, 0.993309, -0.115486 > < 0.0210231, 0.00776801, 0.994064, -0.108796 > < 0.0205203, 0.00556324, 0.994578, -0.10399 > < 0.0201893, 0.00336515, 0.994974, -0.100134 > < 0.0200222, 0.00115414, 0.995342, -0.0964118 > < 0.0200198, -0.00109005, 0.995757, -0.0920259 > < 0.0201921, -0.00338987, 0.996287, -0.0860922 > < 0.0205605, -0.00577215, 0.996991, -0.0775147 > < 0.0211628, -0.00827151, 0.9979, -0.0647763 > < 0.0220646, -0.0109367, 0.998963, -0.0455373 >
python script ray.py のプログラムは後でまとめて解説します。とりあえずは ray.py は black box と考えます。(-0.05, 0.01) の位置 (1, 0) の方向の光線が (0.0217157, 0.01) の位置でレンズ面とクロスし (0.993309, -0.115486) の方向に光線の向きが変わったことが ray.py によって計算されました。残りの 9 個の光線についても同様に位置と方向が ray.py によって計算されます。
その結果が上の 10 x 4 個の sf 行列数値です。この 10 個の位置と方向の組はレンズの右側での光線追跡のときに必要となるので cntnr{1} に追加しておきます。追加されたことは下のように確認できます。
cntnr{1} sf "cntnr{1}" [[ 10, 4 ]] < 0.0217157, 0.01, 0.993309, -0.115486 > < 0.0210231, 0.00776801, 0.994064, -0.108796 > < 0.0205203, 0.00556324, 0.994578, -0.10399 > < 0.0201893, 0.00336515, 0.994974, -0.100134 > < 0.0200222, 0.00115414, 0.995342, -0.0964118 > < 0.0200198, -0.00109005, 0.995757, -0.0920259 > < 0.0201921, -0.00338987, 0.996287, -0.0860922 > < 0.0205605, -0.00577215, 0.996991, -0.0775147 > < 0.0211628, -0.00827151, 0.9979, -0.0647763 > < 0.0220646, -0.0109367, 0.998963, -0.0455373 >
cntnr{} = ~ray.py(25`mm,-30`mm, 1/1.5, cntnr{1}) sf "cntnr{} = ~ray.py(25`mm,-30`mm, 1/1.5, cntnr{1})" [[ 10, 4 ]] < 0.0233508, 0.00980991, 0.930262, -0.366896 > < 0.024064, 0.0074352, 0.95373, -0.300664 > < 0.0245562, 0.00514126, 0.969033, -0.246932 > < 0.02486, 0.0028951, 0.979815, -0.199904 > < 0.0249925, 0.000672705, 0.987765, -0.15595 > < 0.0249601, -0.00154662, 0.993682, -0.11223 > < 0.0247603, -0.00378462, 0.997821, -0.0659845 > < 0.0243797, -0.00606908, 0.999903, -0.0138973 > < 0.0237877, -0.0084419, 0.998804, 0.0488854 > < 0.0229201, -0.0109757, 0.991379, 0.131027 >
この計算値も次のスクリーンでの決像で必要になるので cntnr{2} に残します
今までと同様に考えて (80mm,0) のスクリーンに到着したときの光線の位置と方向を ray.py に計算させます。ただし平面スクリーンを -1e+10`mm と半径の大きなレンズで代用しました。屈折率は 1 にしました。計算結果は cntnr に追加保存、すなわち cntnr{3} に保存します。
cntnr{} = ~ray.py(80`mm,-1e+10`mm, 1, cntnr{2}) sf "cntnr{} = ~ray.py(80`mm,-1e+10`mm, 1, cntnr{2})" [[ 10, 4 ]] < 0.08, -0.0125326, 0.930262, -0.366896 > < 0.08, -0.0101986, 0.95373, -0.300664 > < 0.08, -0.0089871, 0.969033, -0.246932 > < 0.08, -0.0083547, 0.979815, -0.199904 > < 0.08, -0.00801198, 0.987765, -0.15595 > < 0.08, -0.00776302, 0.993682, -0.11223 > < 0.08, -0.00743754, 0.997821, -0.0659845 > < 0.08, -0.00684213, 0.999903, -0.0138973 > < 0.08, -0.00569065, 0.998804, 0.0488854 > < 0.08, -0.00343168, 0.991379, 0.131027 >
cntnr に光線追跡の計算結果が残っています。下のように確認できます。
cntnr sf "cntnr=<{}>" % BaseArrayDict 0 sf "cntnr{}=rayLineMt" [[ 10, 4 ]] < -0.05, 0.01, 1, 0 > < -0.05, 0.01, 0.999507, -0.0314108 > < -0.05, 0.01, 0.998027, -0.0627905 > < -0.05, 0.01, 0.995562, -0.0941083 > < -0.05, 0.01, 0.992115, -0.125333 > < -0.05, 0.01, 0.987688, -0.156434 > < -0.05, 0.01, 0.982287, -0.187381 > < -0.05, 0.01, 0.975917, -0.218143 > < -0.05, 0.01, 0.968583, -0.24869 > < -0.05, 0.01, 0.960294, -0.278991 > sf "cntnr" % BaseArrayDict 4 % ElementSize [ 4, 10 ],[ 4, 10 ],[ 4, 10 ],[ 4, 10 ] %a < 0 > [[ 10, 4 ]] < -0.05, 0.01, 1, 0 > < -0.05, 0.01, 0.999507, -0.0314108 > < -0.05, 0.01, 0.998027, -0.0627905 > < -0.05, 0.01, 0.995562, -0.0941083 > < -0.05, 0.01, 0.992115, -0.125333 > < -0.05, 0.01, 0.987688, -0.156434 > < -0.05, 0.01, 0.982287, -0.187381 > < -0.05, 0.01, 0.975917, -0.218143 > < -0.05, 0.01, 0.968583, -0.24869 > < -0.05, 0.01, 0.960294, -0.278991 > %a < 1 > [[ 10, 4 ]] < 0.0217157, 0.01, 0.993309, -0.115486 > < 0.0210231, 0.00776801, 0.994064, -0.108796 > < 0.0205203, 0.00556324, 0.994578, -0.10399 > < 0.0201893, 0.00336515, 0.994974, -0.100134 > < 0.0200222, 0.00115414, 0.995342, -0.0964118 > < 0.0200198, -0.00109005, 0.995757, -0.0920259 > < 0.0201921, -0.00338987, 0.996287, -0.0860922 > < 0.0205605, -0.00577215, 0.996991, -0.0775147 > < 0.0211628, -0.00827151, 0.9979, -0.0647763 > < 0.0220646, -0.0109367, 0.998963, -0.0455373 > %a < 2 > [[ 10, 4 ]] < 0.0233508, 0.00980991, 0.930262, -0.366896 > < 0.024064, 0.0074352, 0.95373, -0.300664 > < 0.0245562, 0.00514126, 0.969033, -0.246932 > < 0.02486, 0.0028951, 0.979815, -0.199904 > < 0.0249925, 0.000672705, 0.987765, -0.15595 > < 0.0249601, -0.00154662, 0.993682, -0.11223 > < 0.0247603, -0.00378462, 0.997821, -0.0659845 > < 0.0243797, -0.00606908, 0.999903, -0.0138973 > < 0.0237877, -0.0084419, 0.998804, 0.0488854 > < 0.0229201, -0.0109757, 0.991379, 0.131027 > %a < 3 > [[ 10, 4 ]] < 0.08, -0.0125326, 0.930262, -0.366896 > < 0.08, -0.0101986, 0.95373, -0.300664 > < 0.08, -0.0089871, 0.969033, -0.246932 > < 0.08, -0.0083547, 0.979815, -0.199904 > < 0.08, -0.00801198, 0.987765, -0.15595 > < 0.08, -0.00776302, 0.993682, -0.11223 > < 0.08, -0.00743754, 0.997821, -0.0659845 > < 0.08, -0.00684213, 0.999903, -0.0138973 > < 0.08, -0.00569065, 0.998804, 0.0488854 > < 0.08, -0.00343168, 0.991379, 0.131027 >
ただし、gnuplot で可視化させるためには (cntnr{0}[0,0],cntnr{0}[0,1]), (cntnr{1}[0,0],cntnr{0}[0,1]), ... のような順序で可視化するとき線で結ぶデータが隣り合わせになるようにデータを配置換えしてやる必要があります。
これを手作業で行っていたのでは手間が掛かりすぎるので、下のように sf ループ式を使って、cntr 変数を sf 行列変数に変換してやります。
//@@ N=10 M=!size(cntnr) mat:=<> >> //@@@ copy \#####.### temp.se /y 1 個のファイルをコピーしました。 sf @@temp.se loop count:_n 0 loop count:_n 0 loop count:_n 1 loop count:_n 2 loop count:_n 3 loop count:_n 1 loop count:_n 0 ・ loop count:_n 3 [[ 10, 4, 4 ]] %i < 0, *, * > < -0.05, 0.01, 1, 0 > < 0.0217157, 0.01, 0.993309, -0.115486 > < 0.0233508, 0.00980991, 0.930262, -0.366896 > < 0.08, -0.0125326, 0.930262, -0.366896 > %i < 1, *, * > < -0.05, 0.01, 0.999507, -0.0314108 > < 0.0210231, 0.00776801, 0.994064, -0.108796 > < 0.024064, 0.0074352, 0.95373, -0.300664 > < 0.08, -0.0101986, 0.95373, -0.300664 > %i < 2, *, * > < -0.05, 0.01, 0.998027, -0.0627905 > < 0.0205203, 0.00556324, 0.994578, -0.10399 > < 0.0245562, 0.00514126, 0.969033, -0.246932 > < 0.08, -0.0089871, 0.969033, -0.246932 > %i < 3, *, * > < -0.05, 0.01, 0.995562, -0.0941083 > < 0.0201893, 0.00336515, 0.994974, -0.100134 > < 0.02486, 0.0028951, 0.979815, -0.199904 > < 0.08, -0.0083547, 0.979815, -0.199904 > %i < 4, *, * > < -0.05, 0.01, 0.992115, -0.125333 > < 0.0200222, 0.00115414, 0.995342, -0.0964118 > < 0.0249925, 0.000672705, 0.987765, -0.15595 > < 0.08, -0.00801198, 0.987765, -0.15595 > %i < 5, *, * > < -0.05, 0.01, 0.987688, -0.156434 > < 0.0200198, -0.00109005, 0.995757, -0.0920259 > < 0.0249601, -0.00154662, 0.993682, -0.11223 > < 0.08, -0.00776302, 0.993682, -0.11223 > %i < 6, *, * > < -0.05, 0.01, 0.982287, -0.187381 > < 0.0201921, -0.00338987, 0.996287, -0.0860922 > < 0.0247603, -0.00378462, 0.997821, -0.0659845 > < 0.08, -0.00743754, 0.997821, -0.0659845 > %i < 7, *, * > < -0.05, 0.01, 0.975917, -0.218143 > < 0.0205605, -0.00577215, 0.996991, -0.0775147 > < 0.0243797, -0.00606908, 0.999903, -0.0138973 > < 0.08, -0.00684213, 0.999903, -0.0138973 > %i < 8, *, * > < -0.05, 0.01, 0.968583, -0.24869 > < 0.0211628, -0.00827151, 0.9979, -0.0647763 > < 0.0237877, -0.0084419, 0.998804, 0.0488854 > < 0.08, -0.00569065, 0.998804, 0.0488854 > %i < 9, *, * > < -0.05, 0.01, 0.960294, -0.278991 > < 0.0220646, -0.0109367, 0.998963, -0.0455373 > < 0.0229201, -0.0109757, 0.991379, 0.131027 > < 0.08, -0.00343168, 0.991379, 0.131027 >
この光線追跡データを、最初に作った gnuplot レンズ位置データに追加してやります。そのとき %i < .... > データを空行に置き換えます。行頭の < 文字を取り除いてやります。gnuplot で表示できるフォーマットにするためです。下の gnuplot.ini が出来上がります。
type gnuplot.ini plot "-" using 1:2 with line 0.0222836 0.0114805 0.0212918 0.00870854 0.0205764 0.00585271 0.0201445 0.00294051 0.02 0 0.0201445 -0.00294051 0.0205764 -0.00585271 0.0212918 -0.00870854 0.0222836 -0.0114805 0.0227164, -0.0114805 > 0.0237082, -0.00870854 > 0.0244236, -0.00585271 > 0.0248555, -0.00294051 > 0.025, 0 > 0.0248555, 0.00294051 > 0.0244236, 0.00585271 > 0.0237082, 0.00870854 > 0.0227164, 0.0114805 > -0.05, 0.01, 1, 0 > 0.0217157, 0.01, 0.993309, -0.115486 > 0.0233508, 0.00980991, 0.930262, -0.366896 > 0.08, -0.0125326, 0.930262, -0.366896 > -0.05, 0.01, 0.999507, -0.0314108 > 0.0210231, 0.00776801, 0.994064, -0.108796 > 0.024064, 0.0074352, 0.95373, -0.300664 > 0.08, -0.0101986, 0.95373, -0.300664 > -0.05, 0.01, 0.998027, -0.0627905 > 0.0205203, 0.00556324, 0.994578, -0.10399 > 0.0245562, 0.00514126, 0.969033, -0.246932 > 0.08, -0.0089871, 0.969033, -0.246932 > -0.05, 0.01, 0.995562, -0.0941083 > 0.0201893, 0.00336515, 0.994974, -0.100134 > 0.02486, 0.0028951, 0.979815, -0.199904 > 0.08, -0.0083547, 0.979815, -0.199904 > -0.05, 0.01, 0.992115, -0.125333 > 0.0200222, 0.00115414, 0.995342, -0.0964118 > 0.0249925, 0.000672705, 0.987765, -0.15595 > 0.08, -0.00801198, 0.987765, -0.15595 > -0.05, 0.01, 0.987688, -0.156434 > 0.0200198, -0.00109005, 0.995757, -0.0920259 > 0.0249601, -0.00154662, 0.993682, -0.11223 > 0.08, -0.00776302, 0.993682, -0.11223 > -0.05, 0.01, 0.982287, -0.187381 > 0.0201921, -0.00338987, 0.996287, -0.0860922 > 0.0247603, -0.00378462, 0.997821, -0.0659845 > 0.08, -0.00743754, 0.997821, -0.0659845 > -0.05, 0.01, 0.975917, -0.218143 > 0.0205605, -0.00577215, 0.996991, -0.0775147 > 0.0243797, -0.00606908, 0.999903, -0.0138973 > 0.08, -0.00684213, 0.999903, -0.0138973 > -0.05, 0.01, 0.968583, -0.24869 > 0.0211628, -0.00827151, 0.9979, -0.0647763 > 0.0237877, -0.0084419, 0.998804, 0.0488854 > 0.08, -0.00569065, 0.998804, 0.0488854 > -0.05, 0.01, 0.960294, -0.278991 > 0.0220646, -0.0109367, 0.998963, -0.0455373 > 0.0229201, -0.0109757, 0.991379, 0.131027 > 0.08, -0.00343168, 0.991379, 0.131027 >
これで光線追跡計算結果より可視化データ gnuplot.ini が出来上がったので wgnuplot を起動してやればグラフ画像が得られます。
start wgnuplot
これは実寸に近い形ですが、合焦点付近での光線の様子が判りにくいので縦方向のみを 5 倍にマウスをドラッグして拡大します
意外と大きな合焦点のばらつきが発生しているのが分ります。
cntnr=<{}> cntnr{}=rayLineMt cntnr{} = ~ray.py(20`mm,30`mm, 1.5, rayLineMt) cntnr{} = ~ray.py(25`mm,-30`mm, 1/1.5, cntnr{1}) cntnr{} = ~ray.py(80`mm,-1e+10`mm, 1, cntnr{2}) //@@ N=10 M=!size(cntnr) mat:=<> >> //@@@
レンズの段数が増やしたり合わせレンズにしたりするの光線追跡をするには、 ~ray(..) の引数を、それらにあわせて変えてやり、cntnr{} = ~ray(...) の段数を増やしてやるだけです。是非ともご自分でも色々と遊んで見てください。
この光線追跡の一番の肝は python script:ray.py です。光軸ずれ、球面レンズなど単純な球面レンズ以外の光学系での光線追跡をするには ray.py に修正を加える必要があります。ray.py 自体は単純なプログラムです。自分のray.py もソースごと公開します。高々 70 行弱のプログラムです。是非とも御自分でも ray.py にも修正を加えて実験をしてみてください。
!sin(θa)/!sin(θb) == Va/Vb == Nab <== θb = !asin( !sin(θa)/Nab ) ただし Va: 媒質 A における波の速度 Vb: 媒質 B における波の速度 Nab: Va/Vb θa │en \ra │ \ │ \│ ──────── │\ │ \ │rb\ │θb\
上の sin(θ) と屈折率を関係付ける方法を使うと sin/cos/tan と、その逆関数を含んだ二次方程式を計算する必要が出てきます。これだと計算速度は高速ですが python script が複雑になります。
python はベクトル/行列を扱えます。与えられた関数の値が 0 ベクトルとなる位置をもとめる solver:fsolve:Non-linear multi-variable equation が python に備わっています。この fsolve を使えば ra を入射方向の単位 rb を出射方向の単位ベクトルとすると外積演算 x に対して下の条件を満たす射出光の方向を表す単位ベクトル rb を fsolve に計算させられます。
en x ra = en x (Nab rb) ∴ en x (ra - Nab rb) == 0 即ち en[0]*(ra - Nab rb)[1] - en[1]*(ra - Nab rb)[0] == 0
こうすることで python script を単純化できます。script の見通しを良くできます。この外積関係を使うために光線追跡の行列データを 位置と方向の単位ベクトルで表したのでした。位置と角度は使わなかったのでした。
python を分りやすい形で記述できたのですから、是非とも ray.py をご自分の用途に合わせて改変してみてください。
ray.py のソースを下に示します。なお、この python script の著作権は、私 kVerifierLab:小林が所有します。しかし GPL 条件で公開します。商用に使う以外はご自由に改変してください。
# -*- encoding: cp932 -*- import sfVal as Sf import scipy as Sc dctAt = Sf.getSf("_arg") lensPos = dctAt[1] radius = dctAt[2] refraction = dctAt[3] arRay = dctAt[4] arRet = arRay.copy() arX0=None # ray trace start position arV0 = None # ray trace start direction norm 1 vector eVertical = None # def lensSurface(y): if ( radius >= 0): return (lensPos + radius) - Sc.sqrt( (lensPos + radius)**2 - (y**2+lensPos**2+ 2*lensPos*radius) ) else: return (lensPos + radius) + Sc.sqrt( (lensPos + radius)**2 - (y**2+lensPos**2+ 2*lensPos*radius) ) def norm(x): return Sc.sqrt(sum(x**2)) def getCrossPoint(t): #における y=tan(θ)(x-x0) + y0 の直線上の点の座標 assert(abs(arV0[0]) > 1e-8),"You set vertical ray line" arLine = arX0+ norm(t)* arV0 # ,y> における lensPos, radius 球面レンズの表面点の座標 #print "debug:",(lensPos + radius) - Sc.sqrt( (lensPos + radius)**2 - # (x[1]**2+lensPos**2+ 2*lensPos*radius) ) arLens = Sc.array([lensSurface(t[1]+arX0[1]), arX0[1]+t[1] ]) arAt = arLine - arLens return arAt #return arAt * arAt def bend(theta): bendedDirction =[ Sc.cos(theta)*refraction, Sc.sin(theta)*refraction] return ( eVertical[0]*(arV0[1]-bendedDirction[1]) - eVertical[1]*(arV0[0]-bendedDirction[0]) ) from scipy.optimize import fsolve t = Sc.zeros(2,Sc.Float64) for i, (x0,y0,vx0,vy0) in enumerate(arRay): arX0 = Sc.array([x0,y0],Sc.Float64) arV0 = Sc.array([vx0,vy0],Sc.Float64) t = fsolve(getCrossPoint,t) # 戻り値は二次元 array arCalculationError = getCrossPoint(t) #print "arCalculationError:", sum(arCalculationError**2) assert( sum(arCalculationError**2) < 1e-6) # calcurate direction at lens surface xLensCrossPoint = arX0 + t # vertical line vector at lens surface point eVertical = xLensCrossPoint - Sc.array([radius+lensPos,0] , Sc.Float64) eVertical = eVertical/Sc.sqrt(sum(eVertical**2)) # normlize if eVertical[0] > 0: eVertical = -eVertical # make the eVertical direction same as arV0 #print "eVertical:", eVertical #print "arV0:", arV0 refractedTheta = fsolve(bend, 0) assert( abs(bend(refractedTheta)) < 1e-4), str(bend(refractedTheta)) bendedDirction =[ Sc.cos(refractedTheta), Sc.sin(refractedTheta) ] arRet[i,:] = [ xLensCrossPoint[0],xLensCrossPoint[1] ,bendedDirction[0],bendedDirction[1] ] Sf.putSf(arRet)
sfVal.py は sf 変数を python で読み書きするための python scirpt です。Sf.getSf("_arg") により sf dictionary 変数 _arg.val を読み取り sfVal.py::ClDct クラスインスタンスを返します
~ray.py(20`mm,30`mm, 1.5, rayLineMt)
dctAt = Sf.getSf("_arg") lensPos = dctAt[1] # 20`mm == 20 * 0.01 == 0.2 radius = dctAt[2] # 30`mm == 30 * 0.01 == 0.3 refraction = dctAt[3] # 1.5 arRay = dctAt[4] # rayLineMt 行列
最後の下のコードにより 4x10 python 行列 arRet に蓄えられた光線のクロス位置と光線方向のデータが _rs.val sf 行列変数がカレント・ディレクトリに作られます。
Sf.putSf(arRet)
getCrossPoint(t) はレンズと光線がクロスする点を求めるために設けた関数です。引数 t には位置座標の (x,y) python array 値を与えます。t[0]:x 座標値より定まる光線上の位置ベクトルと、t[1]:y 座標値より定まるレンズ上の位置ベクトル、二つの位置ベクトルの差分を返します。この差分が 0 になる点が光線とレンズが交わる位置ベクトルになります。下の fsolve(.) によって光線とレンズの交点を計算させます。
t = fsolve(getCrossPoint,t) # 戻り値は二次元 array
レンズ位置:lens Pos, レンズの半径, radius光線の開始位置:arX0, 光線の方向:arV0 変数を global 変数として getCrossPoint(.) 関数から参照できるようにしています。光線とレンズの交点を求めるには光線の開始位置と方向が必要です。レンズ位置と半径が必要です。しかし fsolve(.) が働くためには getCrossPoint(.) 関数の引数は二次元 array しか許されません。光線の開始位置などを getCrossPoint(.) 関数の引数として渡せません。このため レンズ位置,レンズの半径,光線の開始位置, 光線の方向を global にしなければなりません。
光線とレンズの交点で、入射光の方向とレンズの法線方向と屈折率より射出光の方向をあらわす単位ベクトルを求めます。bend() の引数は scalar 値一つだけでなければならないので、ibend() の入力値は X 軸に対する射出光の角度にしています。下の fsolve(.) によって射出方向の X 軸に対する角度を求めます。
refractedTheta = fsolve(bend, 0)
bend(.) の戻り値を外積で計算した Snell の法則の値にしています。
getCrossPoint(.) のときと同様な理由で、入射光の方向:arV0 とレンズの法線方向:eVertical と屈折率:refraction は global 変数にしています。
混戦追跡作業は自動化できます。今回はレンズが一枚と単純であることよりコンソールからの sf コマンドを何回実行させることで光線追跡を行わせました。レンズ枚数が 10 枚などと増えてくると手作業でやっていられなくなります。sf でのコマンド操作は bat ファイルで自動化できます。gnuplot.ini ファイルへの混戦追跡データの貼り付けと編集は、そのための phthon script を書くことで自動化できます。レンズ枚数が増えて、その手間の増大に耐えられないときは、自動化に挑戦してみてください。簡単です。
関数 lensSurface(y) 関数を変えてやることで非球面レンズなで任意の光学系の光線追跡が可能になります。fsolve を使って交点を計算させているからこそ許されることです。
光軸が存在するときの光線追跡を行うには、ray.py に軸ずれパラメータを追加せねばなりません。その改造はかんたんでしょう。ベクトル演算形式で記述してあるので、三次元での光線追跡に拡張することも簡単です。是非とも ray.py の改変にチャレンジしてみてください。