在上期文章中,我以不透明挡板上一定尺寸的矩形通孔为例,直观呈现了光线入射时其对光的衍射现象,仿真视频如下所示。
在本期文章中,我将直接进行实现该仿真的源代码介绍。
1. 总述实现该仿真的代码共分为三部分,分别为mplwidget.py、UiMainApp.py和 rectangular aperture diffraction.py,放置在名为Python and Optics的文件夹中,运行主程序rectangular aperture diffraction.py即可调用mplwidget.py和UiMainApp.py。
2.“画布”创建:mplwidget.py
我们将绘制大量的图形和图表,所有这些都将使用Python中的Matplotlib模块来绘制。我们需要创建一个“画布”来绘制这些Matplotlib图形。
为此,我们编写一个Python代码,将Matplotlib嵌入到PyQt5 GUI中。编写完成后,我们将此代码命名为mplwidget.py,并将其保存在文件夹Python and Optics中,后续相关代码也需保存在该文件夹中;或者直接将该代码放在Python搜索导入模块路径中的某个位置,以便后续调用。
编写mplwidget.py之初,需调用相关库函数:
from PyQt5.QtWidgets import QSizePolicy , QWidget , QVBoxLayout
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5 import NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure
from matplotlib import rcParams
随后,设计“画布”的布局:
class MplCanvas(FigureCanvas):
def __init__(self):
# setup Matplotlib Figure and Axis
self.fig = Figure()
self.ax = self.fig.add_subplot(111)
# initialization of the canvas
FigureCanvas.__init__(self, self.fig)
# we define the widget as expandable
FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding)
# notify the system of updated policy
FigureCanvas.updateGeometry(self)
class MPL_WIDGET(QWidget):
def __init__(self, parent=None):
# initialization of Qt MainWindow widget
QWidget.__init__(self, parent)
# set the canvas to the Matplotlib widget
self.canvas = MplCanvas()
# create a navigation toolbar for our plot canvas
self.navi_toolbar = NavigationToolbar(self.canvas,self)
# create a vertical box layout
self.vbl = QVBoxLayout()
# add mpl widget to vertical box
self.vbl.addWidget(self.canvas)
# add the navigation toolbar to vertical box
self.vbl.addWidget(self.navi_toolbar)
# set the layout to vertical box
self.setLayout(self.vbl)
两部分结合得到的完整代码如下:
from PyQt5.QtWidgets import QSizePolicy , QWidget , QVBoxLayout
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5 import NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure
from matplotlib import rcParams
rcParams['font.size'] = 9
class MplCanvas(FigureCanvas):
def __init__(self):
# setup Matplotlib Figure and Axis
self.fig = Figure()
self.ax = self.fig.add_subplot(111)
# initialization of the canvas
FigureCanvas.__init__(self, self.fig)
# we define the widget as expandable
FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding)
# notify the system of updated policy
FigureCanvas.updateGeometry(self)
class MPL_WIDGET(QWidget):
def __init__(self, parent=None):
# initialization of Qt MainWindow widget
QWidget.__init__(self, parent)
# set the canvas to the Matplotlib widget
self.canvas = MplCanvas()
# create a navigation toolbar for our plot canvas
self.navi_toolbar = NavigationToolbar(self.canvas,self)
# create a vertical box layout
self.vbl = QVBoxLayout()
# add mpl widget to vertical box
self.vbl.addWidget(self.canvas)
# add the navigation toolbar to vertical box
self.vbl.addWidget(self.navi_toolbar)
# set the layout to vertical box
self.setLayout(self.vbl)
作为一个矩孔衍射的例子,我们需要设计五种调节控件来描述衍射现象发生时的参数,具体包括:
(1) 入射光的波长λ,(2) 矩孔的宽度b,(3) 矩孔的高度h,(4) 观察屏的尺寸a,以及 (5) 透镜焦距f2 (后续会说明为什么会有这个量)。
这五个控件的布局如下图所示:
实现代码包含在UiMainApp.py中,源代码为:
from PyQt5 import QtCore , QtGui , QtWidgets
from mplwidget import MPL_WIDGET #calling the mplwidget.py
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(691, 545)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.horizontalLayout = QtWidgets.QHBoxLayout(self.centralwidget)
self.horizontalLayout.setObjectName("horizontalLayout")
self.mplwidget = MPL_WIDGET(self.centralwidget)
self.mplwidget.setObjectName("mplwidget")
self.horizontalLayout.addWidget(self.mplwidget)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 691, 21))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.settings = QtWidgets.QDockWidget(MainWindow)
self.settings.setObjectName("settings")
self.dockWidgetContents = QtWidgets.QWidget()
self.dockWidgetContents.setObjectName("dockWidgetContents")
self.verticalLayout = QtWidgets.QVBoxLayout(self.dockWidgetContents)
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
self.verticalLayout.setObjectName("verticalLayout")
self.label_lamda = QtWidgets.QLabel(self.dockWidgetContents)
self.label_lamda.setObjectName("label_lamda")
self.verticalLayout.addWidget(self.label_lamda)
self.SpinBox_lambda = QtWidgets.QdoubleSpinBox(self.dockWidgetContents)
self.SpinBox_lambda.setDecimals(0)
self.SpinBox_lambda.setMinimum(400.0)
self.SpinBox_lambda.setMaximum(900.0)
self.SpinBox_lambda.setSingleStep(10.0)
self.SpinBox_lambda.setProperty("value", 560.0)
self.SpinBox_lambda.setObjectName("SpinBox_lambda")
self.verticalLayout.addWidget(self.SpinBox_lambda)
self.slider_lambda = QtWidgets.QSlider(self.dockWidgetContents)
self.slider_lambda.setMinimum(400)
self.slider_lambda.setMaximum(900)
self.slider_lambda.setSingleStep(10)
self.slider_lambda.setProperty("value", 560)
self.slider_lambda.setOrientation(QtCore.Qt.Horizontal)
self.slider_lambda.setTickPosition(QtWidgets.QSlider.TicksBelow)
self.slider_lambda.setTickInterval(50)
self.slider_lambda.setObjectName("slider_lambda")
self.verticalLayout.addWidget(self.slider_lambda)
self.label_b = QtWidgets.QLabel(self.dockWidgetContents)
self.label_b.setObjectName("label_b")
self.verticalLayout.addWidget(self.label_b)
self.SpinBox_b = QtWidgets.QDoubleSpinBox(self.dockWidgetContents)
self.SpinBox_b.setDecimals(1)
self.SpinBox_b.setMinimum(0.1)
self.SpinBox_b.setSingleStep(1.0)
self.SpinBox_b.setProperty("value", 4.0)
self.SpinBox_b.setObjectName("SpinBox_b")
self.verticalLayout.addWidget(self.SpinBox_b)
self.slider_b = QtWidgets.QSlider(self.dockWidgetContents)
self.slider_b.setMinimum(1)
self.slider_b.setMaximum(100)
self.slider_b.setProperty("value", 4)
self.slider_b.setOrientation(QtCore.Qt.Horizontal)
self.slider_b.setTickPosition(QtWidgets.QSlider.TicksBelow)
self.slider_b.setTickInterval(10)
self.slider_b.setObjectName("slider_b")
self.verticalLayout.addWidget(self.slider_b)
self.label_h = QtWidgets.QLabel(self.dockWidgetContents)
self.label_h.setObjectName("label_h")
self.verticalLayout.addWidget(self.label_h)
self.SpinBox_h = QtWidgets.QDoubleSpinBox(self.dockWidgetContents)
self.SpinBox_h.setDecimals(1)
self.SpinBox_h.setMinimum(0.1)
self.SpinBox_h.setProperty("value", 4.0)
self.SpinBox_h.setObjectName("SpinBox_h")
self.verticalLayout.addWidget(self.SpinBox_h)
self.slider_h = QtWidgets.QSlider(self.dockWidgetContents)
self.slider_h.setMinimum(1)
self.slider_h.setMaximum(100)
self.slider_h.setProperty("value", 4)
self.slider_h.setOrientation(QtCore.Qt.Horizontal)
self.slider_h.setTickPosition(QtWidgets.QSlider.TicksBelow)
self.slider_h.setTickInterval(10)
self.slider_h.setObjectName("slider_h")
self.verticalLayout.addWidget(self.slider_h)
self.label_a = QtWidgets.QLabel(self.dockWidgetContents)
self.label_a.setObjectName("label_a")
self.verticalLayout.addWidget(self.label_a)
self.SpinBox_a = QtWidgets.QDoubleSpinBox(self.dockWidgetContents)
self.SpinBox_a.setDecimals(1)
self.SpinBox_a.setMinimum(0.1)
self.SpinBox_a.setSingleStep(1.0)
self.SpinBox_a.setProperty("value", 15.0)
self.SpinBox_a.setObjectName("SpinBox_a")
self.verticalLayout.addWidget(self.SpinBox_a)
self.slider_a = QtWidgets.QSlider(self.dockWidgetContents)
self.slider_a.setMinimum(1)
self.slider_a.setMaximum(100)
self.slider_a.setProperty("value", 15)
self.slider_a.setOrientation(QtCore.Qt.Horizontal)
self.slider_a.setTickPosition(QtWidgets.QSlider.TicksBelow)
self.slider_a.setTickInterval(10)
self.slider_a.setObjectName("slider_a")
self.verticalLayout.addWidget(self.slider_a)
self.label_f2 = QtWidgets.QLabel(self.dockWidgetContents)
self.label_f2.setObjectName("label_f2")
self.verticalLayout.addWidget(self.label_f2)
self.SpinBox_f2 = QtWidgets.QDoubleSpinBox(self.dockWidgetContents)
self.SpinBox_f2.setDecimals(1)
self.SpinBox_f2.setMinimum(0.1)
self.SpinBox_f2.setMaximum(50.0)
self.SpinBox_f2.setProperty("value", 2.0)
self.SpinBox_f2.setObjectName("SpinBox_f2")
self.verticalLayout.addWidget(self.SpinBox_f2)
self.slider_f2 = QtWidgets.QSlider(self.dockWidgetContents)
self.slider_f2.setMinimum(1)
self.slider_f2.setMaximum(50)
self.slider_f2.setProperty("value", 2)
self.slider_f2.setOrientation(QtCore.Qt.Horizontal)
self.slider_f2.setTickPosition(QtWidgets.QSlider.TicksBelow)
self.slider_f2.setTickInterval(10)
self.slider_f2.setObjectName("slider_f2")
self.verticalLayout.addWidget(self.slider_f2)
self.settings.setWidget(self.dockWidgetContents)
MainWindow.addDockWidget(QtCore.Qt.DockWidgetArea(1), self.settings)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.settings.setWindowTitle(_translate("MainWindow", "Settings"))
self.label_lamda.setText(_translate("MainWindow", "<html ><head / > < body > < palign =\"center\">Wave length(10e−9 m)</p> < / body > < / html > "))
self.slider_lambda.setStatusTip(_translate("MainWindow", "Change the wavelength of monochromatic light"))
self.label_b.setText(_translate("MainWindow", "<html ><head/ > < body > < palign =\"center\">b(10e−5 m)</p></body ></html> "))
self.SpinBox_b.setStatusTip(_translate("MainWindow", "Change the width of the rectangular aperture"))
self.slider_b.setStatusTip(_translate("MainWindow", "Change the width of the rectangular aperture"))
self.label_h.setText(_translate("MainWindow", "<html ><head/ > < body > < palign =\"center\">h(10e−5 m)</p></body ></html> "))
self.SpinBox_h.setStatusTip(_translate("MainWindow", "change the height of the recatangular aperture"))
self.slider_h.setStatusTip(_translate("MainWindow", "change the height of the recatangular aperture"))
self.label_a.setText(_translate("MainWindow", "<html ><head/ > < body > < palign =\"center\">a(10e−2 m)</p></body ></html> "))
self.SpinBox_a.setStatusTip(_translate("MainWindow", "Change the side of the square−shaped screen"))
self.slider_a.setStatusTip(_translate("MainWindow", "Change the side of the square−shaped screen"))
self.label_f2.setText(_translate("MainWindow", "<html ><head/ > < body > < palign =\"center\">f2(m)</p></body ></html >"))
self.SpinBox_f2.setStatusTip(_translate("MainWindow", "Change the focal length f2 of the lens L2"))
self.slider_f2.setStatusTip(_translate("MainWindow", "Change the focal length f2 of the lens L2"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
此处的衍射公式采用的是夫琅禾费近似。
夫琅禾费衍射是一种使用惠更斯-菲涅耳原理,藉以把通过方孔、圆孔或狭缝的波动分成多个向外的波动,使用透镜来有目的地观察衍射光的观测实验。
具体的衍射公式在rectangular aperture diffraction.py中,源代码如下:
from PyQt5.QtWidgets import QApplication , QMainWindow
# Import pyqtSlot to connect sliders and DoubleSpinBox signals
from PyQt5.QtCore import pyqtSlot
# Import Ui_MainWindow class from UiMainApp.py generated by uic module
from UiMainApp import Ui_MainWindow
# Import functions from numpy library for scientific simulation
from numpy import pi, linspace ,meshgrid ,sin
# Import matplotlib.cm for the color map in our image of diffraction
import matplotlib.cm as cm
class MainApp(QMainWindow , Ui_MainWindow):
"""
MainApp class inherit from QMainWindow and from
Ui_MainWindow class in UiMainApp module.
"""
def __init__(self):
"""Constructor or the initializer"""
QMainWindow.__init__(self)
# It is imperative to call self.setupUi (self) for the interface to initialize.
self.setupUi(self) # This is defined in design.py file automatically
self.fig1()
def fig1(self): # Fraunhofer diffraction
lamda = self.slider_lambda.value()*1.E-9;k = (2.*pi) / lamda #wavelength of light in vaccuum
b = self.slider_b.value()*1.E-5;
h = self.slider_h.value()*1.E-5 # dimensions of diffracting rectangular aperture (m)
# b is along (Ox) and h is along (Oy)
f_2 = self.slider_f2.value() # f2 is the focal length of the lens L2(m)
a = self.slider_a.value()*1.E-2 # Side of a square−shaped screen(m)
X_Mmax = a / 2.;X_Mmin = -a / 2.
Y_Mmax = X_Mmax;Y_Mmin = X_Mmin
N = 400
X = linspace(X_Mmin, X_Mmax, N);Y = X # coordinates of screen
B = (k*b*X) / (2.*f_2);H = (k*h*Y) / (2.*f_2) # intermediate variable
# 2D representation
BB, HH = meshgrid(B, H)
I = ((sin(BB) / BB)**2)*((sin(HH) / HH)**2)
# figure 2D
mpl = self.mplwidget.canvas
mpl.ax.clear()
mpl.ax.imshow(I, cmap=cm.gray, interpolation='bilinear',origin ='lower', vmin = 0, vmax = .01)
mpl.ax.set_xlabel(u'$X(m)$', fontsize = 12, fontweight ='bold')
mpl.ax.set_ylabel(u'$Y(m)$', fontsize = 12, fontweight ='bold')
mpl.ax.set_xticks(linspace(0, N, 5))
mpl.ax.set_xticklabels(linspace(X_Mmin, X_Mmax, 5), color='r')
mpl.ax.set_yticks(linspace(0, N, 5))
mpl.ax.set_yticklabels(linspace(Y_Mmin, Y_Mmax, 5), color='r')
mpl.figure.suptitle('Fraunhofer Diffraction by rectangular aperture | Zhihu ID: CHEN', fontsize = 14, fontweight ='bold')
mpl.ax.set_title(r"$\lambda = %.3e \ m, \ b = %.2e \ m, \ h= % .2e \ m, \ f_2 = % .1f \ m$"% (lamda ,b, h, f_2),fontsize=10)
mpl.draw()
# DoubleSpinBox signals
@ pyqtSlot("double")
def on_SpinBox_lambda_valueChanged(self, value):
self.slider_lambda.setValue(value)
@ pyqtSlot("double")
def on_SpinBox_b_valueChanged(self, value):
self.slider_b.setValue(value)
@ pyqtSlot("double")
def on_SpinBox_h_valueChanged(self, value):
self.slider_h.setValue(value)
@ pyqtSlot("double")
def on_SpinBox_a_valueChanged(self, value):
self.slider_a.setValue(value)
@ pyqtSlot("double")
def on_SpinBox_f2_valueChanged(self, value):
self.slider_f2.setValue(value)
# Sliders signals
@ pyqtSlot("int")
def on_slider_lambda_valueChanged(self, value):
self.SpinBox_lambda.setValue(value)
self.fig1()
@ pyqtSlot("int")
def on_slider_b_valueChanged(self, value):
self.SpinBox_b.setValue(value)
self.fig1()
@ pyqtSlot("int")
def on_slider_h_valueChanged(self, value):
self.SpinBox_h.setValue(value)
self.fig1()
@ pyqtSlot("int")
def on_slider_a_valueChanged(self, value):
self.SpinBox_a.setValue(value)
self.fig1()
@ pyqtSlot("int")
def on_slider_f2_valueChanged(self, value):
self.SpinBox_f2.setValue(value)
self.fig1()
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
MyApplication = MainApp()
MyApplication.show() # Show the form
sys.exit(app.exec_()) # Execute the app
【1】注:本文的所有代码,大部分会包含注释,其余没包含注释的语句,可以留言咨询,第一次写文章专门介绍代码,没有相关经验,望大家海涵!欢迎读者在评论区友善讨论,我会热情回复。共勉!
【2】注:欢迎关注我的知乎账号,id:CHEN
,