シミュレーションで理解するヤングの干渉実験
ヤングの干渉実験
ヤングの干渉実験の対象となる光は波です。波であるならば以下の二つの性質を持ちます。
- 回折:直線的に進むことなく、物体の裏側に回り込みながら伝わっていくこと。
- 干渉:異なる波が重ね合わさり、それぞれが強めあったり弱めあったりすること。
この回折と干渉の原理を通じて光の干渉をスクリーン上に反映させる実験があります。それがヤングの干渉実験と呼ばれるものです。実験系を紹介します。
点光源から放たれた光はスリットが二つ設けられている二重スリットマスクまで伝搬し、マスクを通過した光は各々のスリットから回折しながらスクリーンへとさらに伝搬して行く流れです。ここでスクリーンに現れる像を見てましょう。
スクリーン上では明るい部分と暗い部分が交互に現れる干渉縞が確認できます。これは各々のスリットから放たれる光がスクリーン手前で重なり、強めあったり弱めあったことを示しています。この干渉という現象は波の特性であることを踏まえれば、ヤングの干渉実験は光は波であることを明らかにします。
こちらの像はシミュレーションした結果となります。光が波であることを前提にどのように伝搬してこの干渉縞が生じたか、理論的に説明していこうと思います。
光波の伝搬理論
ここから光は波だということを意識して「光」と「光波」を使い分けていこうと思います。
原点は波動方程式
光波
これは波動方程式と呼ばれ、光波だけでなく音波や弾性波などの重ね合わせの法則を満たす波動の基本的な方程式です。
・平面波
・球面波
球面波は再度ホイヘンス・フレネルの原理の説明で現れます。これらの波を見てわかるのは指数関数
より
この指数関数の性質を使って議論を簡単にしましょう。波動を次のように空間成分
平面波や球面波がこの形に分解できるのは明確です。空間成分
という方程式が導けます。これはヘルムホルツ方程式と呼ばれ、これを解くことで光波が空間中でどのように広がるか(伝搬するか)求めることができます。
最後に強度を紹介します。強度は次の定義です。
強度は光のエネルギーを表しており、我々がスクリーン上で観測するものはこの強度になります。
ホイヘンス-フレネルの原理
ヘルムホルツ方程式をベースとして導かれる波動の伝搬結果は次の通りです。
これはホイヘンス-フレネルの原理を表した数式です。面
ベクトル
物理的な意味を把握するならば、ホイヘンス-フレネルの原理はこの表現で良いですが、光波を解析することを考えるならば、伝搬する方向を明確にしたほうが扱いやすいです。よって伝搬方向を
フレネル回折とフラウンホーファー回折
先ほどの
と書けます。ここで
を定義すれば、
という
と書けます。この式展開は記事の最後に紹介してある参考文献[1][2]を見てください。この近似に従う伝搬はフレネル回折と呼ばれます。最初に仮定した
と書けます。この式に従う伝搬はフラウンホーファー回折と呼びます。例えば、はるか遠くの星が放った光が地球に届いたとき、その光はフラウンホーファー回折に従います。ここでさらに、
とおいてやると、フラウンホーファー回折は
と書けます。これは
であり、フラウンホーファー回折とフーリエ変換が同一であることが確認できます。プログラムとして実行することを考えると、フーリエ変換はライブラリとして提供されているものがいくつもあるので、それをそのまま使えばフラウンホーファー回折は一発でシミュレーションできます。ここから相対的な強度が重要であるという観点から定数
二重スリットから伝搬する光の解析解
二重スリットとスクリーンの間の距離が十分に離れていると仮定します[3]。要は二重スリットから伝搬する光はフラウンホーファー回折(二次元フーリエ変換)に従うと仮定するわけです。二重スリットのフーリエ変換は解析的に求めることができ、この解析解を使って後でシミュレーションするフラウンホーファー回折の結果と比較する予定です。まずスリットを表現するために矩形関数を紹介します。矩形関数は次の定義です。
図化すると次のような形です。
となります。積分範囲は明確にしてませんが
です[4]。図化すると次のような概形です。
この矩形関数を使うことでスリット一つは
と書けます。二重スリットは、各スリットが
と書けます。このそして二重スリットからスクリーンまで距離
が得られます。これが求めたかった解析解です。
ヤングの干渉実験のシミュレーション
シミュレーション方法
フラウンホーファー回折のシミュレーションを通じてヤングの干渉実験の結果を得たいと思います。言語はPythonです。まず二重スリットをndarrayで作成します。
白の領域がスリットでいままで
# フーリエ変換を行うモジュールをimport
import numpy.fft as fft
# ここらへん諸々のコード
# フラウンホーファー回折のシミュレーション
result = fft.fftshift(fft.fft2(fft.fftshift(double_slit_mask)))
double_slit_mask
は作成した二重スリットマスクです。result
はフラウンホーファー回折した結果です。フラウンホーファー回折はこのように一行で完結させることができます。
このシミュレーション結果をどのように参照しましょうか?result
は複素数を要素とする配列なので分かりやすい結果として何か処理を施す必要があります。スクリーン上に反映されるのは光のエネルギーだと言いました。このresult
を光のエネルギーとして見れれば回折結果としてよさそうです。光波
でした。強度はコード上ではimport numpy as np
を宣言したならば、
intensity = np.abs(result)**2
で計算可能です。実際にこの強度分布を見れば干渉縞が確認できます。
シミュレーションと解析解の比較
シミュレーションを行う際、気を付けるべきは以下の二点です。
- 1ピクセル当たりの長さはできるだけ小さくすること。
- 二重スリット周りはできるだけゼロパディングすること。
これらはエイリアシング[5]を起こさせないかつフラウンホーファー回折後の分解能を高くするために行います。簡単に言えばシミュレーションの精度を上げるためです。今回のシミュレーションにて作成した二重スリットマスクは以下の通りです。
1ピクセルの幅はx軸方向もy軸方向も0.01mmで設定
この時フラウンホーファー回折後に得られる強度像は以下の通りです。
上:シミュレーション結果、下:解析解、右:計算結果拡大、左:計算結果
スクリーンから1m伝搬したと仮定、表示上は白だが波長は赤色(700nm)で計算
こうしてシミュレーション結果と解析解を比較してみると見た感じ同一の結果が得られていることが分かります。このようにして計算機上で干渉縞を得ることが可能です。
最後に
ヤングの干渉実験の傍ら、フラウンホーファー回折のシミュレーション方法も説明しました。今回は解析解があるということでシミュレーションを用いることに疑問はあるかもしれませんが、シミュレーションが本領発揮するのは解析解を持たないときです。いろんな形の開口を作ってそれをフーリエ変換させすればそれは立派なフラウンホーファー回折のシミュレーション結果となります。是非皆さんのほうで試してみてください。
Appendix
参考文献
[1] 谷田貝豊彦(著)「光学」株式会社朝倉書店
[2] Joseph W. Goodman(著)尾崎 義治、朝倉 利光 (訳)「フーリエ光学(第3版)」森北出版株式会社
ソースコード
- 実行環境
OS: Windows 11
Python: 3.10.6
numpy: 1.26.1
matplotlib: 3.8.1
パラメータ設定の箇所を設定すればいろいろ実験できます。実行する際は自己責任でお願いいたします。
ソースコード
import numpy as np
import numpy.fft as fft
import matplotlib.pyplot as plt
### パラメータ設定(始)###
space_size: float = 2.5*10 # 伝搬前のxおよびy軸方向の長さ [mm]
pixel_width: float = 0.01 # 伝搬前の1ピクセルのxおよびy軸の長さ [mm]
wavelength: float = 700 * 10**(-6) # 波長 [mm]
prop_distance: float = 1000 # 伝搬距離 [mm]
slit_height: float = 0.6 # スリットの高さ(y軸方向の長さ) [mm]
slit_width: float = 0.2 # [mm] スリットの幅(x軸方向の長さ) [mm]
slit_distance: float = 1 # スリット間の幅 [mm]
space_size_expand: float = 6 # 伝搬後強度拡大表示時のxおよびy軸方向の長さ [mm]
### パラメータ設定(終)###
# その他シミュレーション準備
x = np.arange(-space_size / 2, space_size / 2, pixel_width)
number_of_points = len(x)
y = x
xx, yy = np.meshgrid(x, y)
# 二重スリットマスク生成用関数
def generate_double_slit_mask() -> np.ndarray:
# 一つ目のスリット(x座標がマイナスになる位置に設置)
slit1_center_x = -slit_distance / 2 # 中心のx座標
slit1_outer_x = slit1_center_x - slit_width / 2 # スリットの外側のx座標
slit1_inner_x = slit1_center_x + slit_width / 2 # スリットの内側のx座標
slit1_top_y = slit_height / 2 # 一番上のy座標
slit1_bottom_y = -slit_height / 2 # 一番下のy座標
# 二つ目のスリット(x座標がプラスになる位置に設置)
slit2_center_x = slit_distance / 2 # 中心のx座標
slit2_outer_x = slit2_center_x + slit_width / 2 # スリットの外側のx座標
slit2_inner_x = slit2_center_x - slit_width / 2 # スリットの内側のx座標
slit2_top_y = slit_height / 2 # 一番上のy座標
slit2_bottom_y = -slit_height / 2 # 一番下のy座標
# 二重スリット生成
slit1_area_judge = ((slit1_outer_x <= xx)
& (xx <= slit1_inner_x)
& (slit1_bottom_y <= yy)
& (yy <= slit1_top_y))
slit2_area_judge = ((slit2_inner_x <= xx)
& (xx <= slit2_outer_x)
& (slit2_bottom_y <= yy)
& (yy <= slit2_top_y))
double_slit_mask = np.where(slit1_area_judge | slit2_area_judge, 1, 0)
return double_slit_mask
# 光の伝搬とその強度の計算
def cal_intensity(mask: np.ndarray) -> np.ndarray:
result = fft.fftshift(fft.fft2(fft.fftshift(mask))) # フラウンホーファー回折
intensity = np.abs(result)**2 # シミュレーション結果の強度
intensity = intensity / np.amax(intensity) # 強度正規化
return intensity
# sinc関数
def sinc(x :np.ndarray) -> np.ndarray:
return np.sin(np.pi * x) / (np.pi * x)
# シミュレーション(伝搬&強度計算)
double_slit_mask = generate_double_slit_mask()
intensity = cal_intensity(double_slit_mask)
# 解析解計算
x_max_diffraction = wavelength * prop_distance / (pixel_width * 2)
x_diffraction = np.linspace(-x_max_diffraction, x_max_diffraction, len(x))
y_diffraction = x_diffraction
xx_diffraction, yy_diffraction = np.meshgrid(x_diffraction, y_diffraction)
xx_spatial_freq = xx_diffraction / (wavelength * prop_distance)
yy_spatial_freq = yy_diffraction / (wavelength * prop_distance)
analytical_prop = 2 * slit_width * slit_height * np.cos(np.pi * slit_distance * xx_spatial_freq) \
* sinc(slit_width * xx_spatial_freq) * sinc(slit_height * yy_spatial_freq) # 解析解
intensity_analytical = np.abs(analytical_prop)**2 # 解析解の強度
intensity_analytical = intensity_analytical / np.amax(intensity_analytical) # 強度正規化
extent_diffraction = [x_diffraction.min(), x_diffraction.max(), y_diffraction.min(), y_diffraction.max()]
# 二重スリット生成確認
plt.subplots()
plt.imshow(double_slit_mask, cmap='binary_r'
,extent=[x.min(), x.max(), y.min(), y.max()])
plt.xlabel('x [mm]')
plt.ylabel('y [mm]')
# スクリーンに映る強度像確認
## シミュレーション強度像参照
plt.subplots()
plt.imshow(intensity, cmap='binary_r'
,extent=extent_diffraction)
plt.xlabel('x [mm]')
plt.ylabel('y [mm]')
plt.title('simulation result')
plt.colorbar(label='Intensity [a.u.]')
## 解析解強度像参照
plt.subplots()
plt.imshow(intensity_analytical, cmap='binary_r'
,extent=extent_diffraction)
plt.xlabel('x [mm]')
plt.ylabel('y [mm]')
plt.title('analytical result')
plt.colorbar(label='Intensity [a.u.]')
indeces = np.where((-space_size_expand / 2 < x_diffraction) & (x_diffraction < space_size_expand / 2))[0]
min_index = indeces[0]
max_index = indeces[-1]
intensity_expand = intensity[min_index:max_index, min_index:max_index]
intensity_analytical_expand = intensity_analytical[min_index:max_index, min_index:max_index]
## シミュレーション(拡大)
min_val = x_diffraction[min_index]
max_val = x_diffraction[max_index]
extent_diffraction = [min_val, max_val, min_val, max_val]
plt.subplots()
plt.imshow(intensity_expand, cmap='binary_r'
,extent=extent_diffraction)
plt.xlabel('x [mm]')
plt.ylabel('y [mm]')
plt.title('simulation result')
plt.colorbar(label='Intensity [a.u.]')
## 解析解(拡大)
plt.subplots()
plt.imshow(intensity_analytical_expand, cmap='binary_r'
,extent=extent_diffraction)
plt.xlabel('x [mm]')
plt.ylabel('y [mm]')
plt.title('analytical result')
plt.colorbar(label='Intensity [a.u.]')
plt.show()
-
単一の周波数のみを持つならば、波長も一つに決まるのでこの光は単色光と呼ばれます。 ↩︎
-
大きさと言っていますが
は複素数であることに注意してください。 ↩︎u(\vec{r}) -
具体的にどの程度二重スリットとスクリーンが離れているか考えたくなると思います。これに関しては数メートル程度ととらえれば良いです。どこかの星と地球くらい離れている必要はありません。 ↩︎
-
を定義とする場合もあります。 ↩︎\text{sinc}(x)=\sin x/x -
エイリアシングについてはこの記事で紹介しました。https://zenn.dev/torataro/articles/2023-06-08-discrete_fourier_transform ↩︎
Discussion