最近做大物实验,萌生了将迈克尔逊干涉仪等倾干涉有关实验利用Python进行仿真的想法。主要利用tkinter库构建UI,matplotlib/numpy库进行计算和绘图,FigureCanvasTkAgg, NavigationToolbar2Tk用于把利用matplotlib库生成的图片绘制到tkinter画布上:

from tkinter import *
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
import matplotlib as mpl
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
import numpy as np
import math

        基本设置:

plt.ion()  # Open the interactive mode.
mpl.use('TkAgg')  # Tkinter & matplotlib linker.

# Control
window = Tk()

fig = Figure()
image = fig.add_subplot()
label = [Label(), Label(), Label(), Label()]
scale = [[Scale(), Scale()],
         [Scale(), Scale()],
         [Scale()],
         [Scale(), Scale()]]

# Data
distance = 0
distance_accuracy = IntVar()
delta_distance = 0

focal = 200
focal_part_1 = DoubleVar()
angle_part_2 = DoubleVar()

wavelength_1 = 0
wavelength_1_part_1 = DoubleVar()
wavelength_1_part_2 = DoubleVar()

wavelength_2 = 0
wavelength_2_part_1 = DoubleVar()
wavelength_2_part_2 = DoubleVar()

VALUE_RANGE = 20
ACCURACY = 500

        首先构建交互界面,在下面的函数中对基本参数做了设置。

def WindowCreate():
    window.title("迈克尔逊干涉仪实验仿真")
    window.geometry("1500x750")
    window.resizable(False, False)

        对界面进行初始化,label和distance_accuracy是定义在函数外的全局变量。用scale调整光的波长和透镜焦距,距离由于需要连续调整,故使用鼠标滚轮控制,变化量由用户通过scale控件决定。

def WindowInitialize():
    global label
    global distance_accuracy

    window.bind('<MouseWheel>', DistanceRefresh)
    window.bind('<ButtonRelease>', WavelengthAndFocalRefresh)

    # Scales control wavelength 1.
    scale[0][0] = Scale(window, length=1000, orient=HORIZONTAL, activebackground='red', from_=0, to=999,
                        digits=3, resolution=1, label='波长1(整数部分)', variable=wavelength_1_part_1)
    scale[0][0].place(x=0, y=0)
    scale[0][1] = Scale(window, length=200, orient=HORIZONTAL, activebackground='red', from_=0, to=0.99,
                        digits=2, resolution=0.01, label='波长1(小数部分)', variable=wavelength_1_part_2)
    scale[0][1].place(x=1100, y=0)

    # Scales control wavelength 2.
    scale[1][0] = Scale(window, length=1000, orient=HORIZONTAL, activebackground='red', from_=0, to=999,
                        digits=3, resolution=1, label='波长2(整数部分)', variable=wavelength_2_part_1)
    scale[1][0].place(x=0, y=80)
    scale[1][1] = Scale(window, length=200, orient=HORIZONTAL, activebackground='red', from_=0, to=0.99,
                        digits=2, resolution=0.01, label='波长2(小数部分)', variable=wavelength_2_part_2)
    scale[1][1].place(x=1100, y=80)

    # Scale controls accuracy of distance.
    scale[2][0] = Scale(window, length=200, orient=HORIZONTAL, activebackground='red', from_=1, to=5,
                        digits=1, resolution=1, label='距离(滚轮控制精度)', variable=distance_accuracy)
    scale[2][0].place(x=0, y=160)

    # Scales control angle.
    scale[3][0] = Scale(window, length=400, orient=HORIZONTAL, activebackground='red', from_=1, to=200,
                        digits=3, resolution=1, label='焦距(整数部分)/mm', variable=focal_part_1)
    scale[3][0].place(x=0, y=240)
    scale[3][0].set(100)
    scale[3][1] = Scale(window, length=200, orient=HORIZONTAL, activebackground='red', from_=0, to=0.9,
                        digits=1, resolution=0.1, label='焦距(小数部分)/mm', variable=angle_part_2)
    scale[3][1].place(x=0, y=320)

    # Label shows the distance.
    label[0] = Label(window, text="{:.2f}".format(wavelength_1) + 'nm', font=("", 20))
    label[0].place(x=1350, y=30)
    label[1] = Label(window, text="{:.2f}".format(wavelength_2) + 'nm', font=("", 20))
    label[1].place(x=1350, y=110)
    label[2] = Label(window, text="{:.5f}".format(distance) + 'mm', font=("", 20))
    label[2].place(x=250, y=190)
    label[3] = Label(window, text="{:.1f}".format(focal) + 'mm', font=("", 20))
    label[3].place(x=250, y=350)

    fig_frame = Frame(window, width=500, height=500)
    fig_frame.place(x=500, y=200)
    canvas = FigureCanvasTkAgg(fig, fig_frame)
    canvas.get_tk_widget().pack(side=TOP, fill=BOTH, expand=YES)
    toolbar = NavigationToolbar2Tk(canvas, fig_frame, pack_toolbar=False)
    toolbar.update()
    toolbar.pack(side=RIGHT)

        每次鼠标按键释放和滚轮滚动都会调用函数刷新图片,最后一行image.cla()不加上(用于删除上次图片数据)会造成严重卡顿,当时在这卡了好久才考虑到这个问题:

def WavelengthAndFocalRefresh(event):
    # Check the change of wavelength and focal length.
    global label
    global wavelength_1
    global wavelength_2
    global focal

    wavelength_1 = wavelength_1_part_1.get() + wavelength_1_part_2.get()
    wavelength_2 = wavelength_2_part_1.get() + wavelength_2_part_2.get()
    focal = focal_part_1.get() + angle_part_2.get()

    label[0].config(text="{:.2f}".format(wavelength_1) + 'nm')
    label[1].config(text="{:.2f}".format(wavelength_2) + 'nm')
    label[3].config(text="{:.1f}".format(focal) + 'mm')

    if 1000 <= event.x <= 1500 and 250 <= event.y <= 750:
        # In the area of the interference image.
        pass

    ImageRefresh()


def DistanceRefresh(event):
    # Check the change of distance.
    global distance
    global delta_distance

    delta_distance = 10 ** (-1 * distance_accuracy.get())

    if event.delta > 0:
        distance += delta_distance
    else:
        distance -= delta_distance
        if distance < 0:
            distance = 0

    distance = float('%.5f' % distance)
    label[2].config(text="{:.5f}".format(distance) + 'mm')

    ImageRefresh()


def ImageRefresh():
    wavelength_A = wavelength_1 * 10 ** -6
    wavelength_B = wavelength_2 * 10 ** -6
    if wavelength_A != 0:
        x_A, y_A = np.meshgrid(np.linspace(-VALUE_RANGE, VALUE_RANGE, ACCURACY),
                               np.linspace(-VALUE_RANGE, VALUE_RANGE, ACCURACY))
        r_A = np.sqrt(x_A ** 2 + y_A ** 2)
        A = np.cos(math.pi * (2 * distance * np.cos(np.arcsin(np.sin(np.arctan(r_A / focal))))) / wavelength_A) ** 2
    else:
        A = 0
    if wavelength_B != 0:
        x_B, y_B = np.meshgrid(np.linspace(-VALUE_RANGE, VALUE_RANGE, ACCURACY),
                               np.linspace(-VALUE_RANGE, VALUE_RANGE, ACCURACY))
        r_B = np.sqrt(x_B ** 2 + y_B ** 2)
        B = np.cos(math.pi * (2 * distance * np.cos(np.arcsin(np.sin(np.arctan(r_B / focal))))) / wavelength_B) ** 2
    else:
        B = 0

    if wavelength_A != 0 or wavelength_B != 0:
        image.imshow(A/2.0+B/2.0)
    fig.canvas.draw()
    image.cla()

        最后调用函数就可以正常运行啦!

WindowCreate()
WindowInitialize()
window.mainloop()

        贴一张结果图(钠黄光双谱线,视见度下降):

利用Python实现迈克尔逊干涉仪实验仿真-LMLPHP

04-17 14:02