MATLAB GUI仿真设计——双缝干涉

MATLAB GUI仿真设计——双缝干涉

本文旨在为读者提供关于MATLAB双缝干涉仿真GUI应用程序的设计思路。该应用程序通过图形用户界面(GUI)模拟经典的物理实验——双缝干涉,允许用户调整相关物理参数,观察并分析干涉现象。

一、双缝干涉原理

1. 波的性质

波是一个或多个物理量的动态扰动,它从平衡状态传播出去。波的传播不涉及质点的移动,而是振动状态的传播。数学上,任何沿一定方向传播的函数形状都可视为波。因此,波函数可表示为:

f ( x , t ) = A cos ⁡ ( ω ( t − x v ) + φ ) f(x,t) = A\cos\left(\omega(t-\frac{x}{v})+\varphi\right) f(x,t)=Acos(ω(tvx)+φ)

波动方程表示为:

∂ 2 f ∂ t 2 = c 2 ∂ 2 f ∂ x 2 \frac{\partial^2 f}{\partial t^2} = c^2 \frac{\partial^2 f}{\partial x^2} t22f=c2x22f

其中,(c)表示波的传播速度。从这个方程可知,在相同的均匀介质中,波的传播速度是恒定的,且波的频率保持不变。

2. 光的干涉

当两波在同一介质中传播并发生重叠时,重叠区域内的介质质点同时受到两波的作用。如果波的振幅不大,那么该区域内的介质质点的振动位移等于单独波动引起的位移的矢量和,这称为波的叠加原理。

当两波频率相同且具有恒定的相位差,并且它们的振动方向不完全垂直时,这两波的叠加会产生新的稳定波形,即发生波的干涉。

双缝干涉是波动光学中的一个重要现象。当光波通过两个相近的狭缝时,从两个狭缝发出的光波在屏幕上相遇并发生干涉,形成明暗相间的干涉条纹。

双缝干涉

3. 干涉光强

简单起见,假设两波表达为:

E 1 = A 1 cos ⁡ ( ω t + φ 1 ) E 2 = A 2 cos ⁡ ( ω t + φ 2 ) E_1 = A_1 \cos(\omega t + \varphi_1) \\ E_2 = A_2 \cos(\omega t + \varphi_2) E1=A1cos(ωt+φ1)E2=A2cos(ωt+φ2)

它们的叠加结果为:

E = E 1 + E 2 = A cos ⁡ ( ω t + φ ) E = E_1 + E_2 = A\cos(\omega t + \varphi) E=E1+E2=Acos(ωt+φ)

其中,
A 2 = A 1 2 + A 2 2 + 2 A 1 A 2 cos ⁡ ( φ 2 − φ 1 ) tan ⁡ φ = A 1 sin ⁡ φ 1 + A 2 sin ⁡ φ 2 A 1 cos ⁡ φ 1 + A 2 cos ⁡ φ 2 A^2 = A_1^2 + A_2^2 + 2A_1A_2\cos(\varphi_2-\varphi_1) \\ \tan \varphi = \frac{A_1\sin \varphi_1 + A_2\sin \varphi_2}{A_1\cos \varphi_1 + A_2\cos \varphi_2} A2=A12+A22+2A1A2cos(φ2φ1)tanφ=A1cosφ1+A2cosφ2A1sinφ1+A2sinφ2

干涉后的光强为:

I = A 2 = A 1 2 + A 2 2 + 2 A 1 A 2 cos ⁡ ( φ 2 − φ 1 ) I = A^2 = A_1^2 + A_2^2 + 2A_1A_2\cos(\varphi_2-\varphi_1) I=A2=A12+A22+2A1A2cos(φ2φ1)

二、交互界面设计

1、界面设计

交互界面如下图所示:

GUI

在左侧设置一个坐标系空间以显示干涉效果。接着,添加一个面板用于参数输入,在该面板中嵌入四个数字编辑框控件以调整干涉参数。再添加一个面板,用于控制干涉动画,其中包含两个复选框控件以选择显示的干涉线类型。此外,设置三个按钮分别用于重置、播放和展示干涉效果。最后,添加一个滑块控件以调整动画播放的速度。

2. 参数传递

在程序运行前,必须先传入参数。这一过程在应用的启动回调函数(startupFcn)中进行,如下所示:

app.Lambda = app.EditField.Value;
app.d = app.dEditField.Value;
app.D = app.DEditField.Value;
app.x = app.xEditField.Value;
app.frame = app.Slider.Value;

这段代码初始化了应用所需的关键参数,例如波长(Lambda)、双缝间距(d)、屏距(D)和屏中心距(x),以及动画播放速度(frame)。

为了实现参数的实时更新,为每个输入字段设置了值改变的回调函数:

% Value changed function for the Lambda (wavelength) edit field
function EditFieldValueChanged(app, event)
    app.Lambda = app.EditField.Value;
end

% Value changed function for the d (slit separation) edit field
function dEditFieldValueChanged(app, event)
    app.d = app.dEditField.Value;
end

% Value changed function for the D (screen distance) edit field
function DEditFieldValueChanged(app, event)
    app.D = app.DEditField.Value;
end

% Value changed function for the x (distance to screen center) edit field
function xEditFieldValueChanged(app, event)
    app.x = app.xEditField.Value;
end

这些函数确保了当用户在界面上更改这些值时,程序内部的对应参数将即时更新。

为了确保模拟显示的合理性,我们还需要设定绘图区域的坐标轴范围,以便能够清楚地展示双缝干涉图案:

xlim(app.UIAxes, [-5, 10]);  % 设置X轴的显示范围
ylim(app.UIAxes, [-5, 5]);   % 设置Y轴的显示范围

这确保了不论参数如何变化,视图都保持在一个合适的显示范围内。

三、仿真代码

1、光的动态传播

(1)双缝前的传播

在仿真模型中,光的传播需要分为两个阶段:双缝前的传播和双缝后的传播。首先,我们来看双缝前光波的动态传播过程。这部分主要涉及到波的传播动画,模拟光波从发射源到达双缝的过程。

在双缝前的传播阶段,我们假设光波以圆形波前向外传播。波峰和波谷以波速(c)向外扩散,波形保持不变。这里,我们使用MATLAB中的fimplicit函数来绘制波前。

下面的代码段演示了如何实现双缝前的光波动态传播:

function WavePT(app,t,WavePeak,WaveTrough,peakColor,troughColor,a,b,X1,X2,Y1,Y2)
    hold(app.UIAxes, 'on');
    for n = t:-2:1
        wavePeak = n * app.Lambda / 2;
        waveTrough = (n-1) * app.Lambda / 2;

        % 绘制波峰
        if WavePeak
            fimplicit(app.UIAxes,@(X,Y) (X-a).^2 + (Y-b).^2 - wavePeak^2, [X1,X2,Y1,Y2], 'Color', peakColor);
        end

        if WaveTrough
            % 绘制波谷
            fimplicit(app.UIAxes,@(X,Y) (X-a).^2 + (Y-b).^2 - waveTrough^2, [X1,X2,Y1,Y2], 'Color', troughColor);
        end
    end
end

在这个函数中,wavePeakwaveTrough变量分别表示波峰和波谷的半径,它们随时间t增大而线性增加。通过fimplicit函数在坐标范围[X1, X2, Y1, Y2]内绘制圆形波前,其中(a, b)是波源的位置。peakColortroughColor参数用于控制波峰和波谷的颜色。

通过这段代码,我们能够模拟光波在双缝前的传播过程,直观地观察波动的传播行为。接下来的部分将继续讨论光波通过双缝后的传播过程。

(2)双缝后的传播

双缝后的传播涉及到光波通过两个狭缝后的干涉效果。当光波通过双缝时,每个狭缝都可以看作是一个新的波源,发出的波将在屏幕上相遇并产生干涉。简言之,双缝之后的传播动画,就是三个光源单独的动画的叠加。

下面的代码段演示了如何实现双缝后的光波动态传播和干涉:

function wave(app,WavePeak,WaveTrough,InterferenceFringes,flag)
    
    hold(app.UIAxes, 'on'); % 保持当前图形,允许在上面绘制更多图形元素

    % 清除上一次的波峰和波谷,以避免画面过于拥挤
    cla(app.UIAxes);
    
    plot(app.UIAxes, [0, 0], [-app.d/2, app.d/2], 'k', 'LineWidth', 2);
    plot(app.UIAxes, [0, 0], [app.d/2 + 0.2, 5], 'k', 'LineWidth', 2);
    plot(app.UIAxes, [0, 0], [-app.d/2 - 0.2, -5], 'k', 'LineWidth', 2);

    % 如果colorState字段不存在,则初始化它
    if ~isfield(app, 'colorState')
        app.colorState = true; % true 表示本次绘制波峰为红色,波谷为蓝色
    end

    % 根据colorState的值选择颜色
    if app.colorState
        peakColor = 'r';
        troughColor = 'b';
    else
        peakColor = 'b';
        troughColor = 'r';
    end

    % 绘制波峰和波谷
    if flag == 0
        WavePT(app,app.t,WavePeak,WaveTrough,peakColor,troughColor,-5,0,-5,0,-5,5)
    elseif flag == 1
        WavePT(app,app.t,WavePeak,WaveTrough,peakColor,troughColor,-5,0,-5,0,-5,5)
        WavePT(app,app.T,WavePeak,WaveTrough,peakColor,troughColor,0,app.d/2,0,app.D,-5,5)
        WavePT(app,app.T,WavePeak,WaveTrough,peakColor,troughColor,0,-app.d/2,0,app.D,-5,5)
    end
    % 切换颜色状态
    app.colorState = ~app.colorState;

    drawnow;
end

在这段代码中,ab 代表波源位置,app.d 为双缝间的距离。通过在不同的Y坐标(b - app.d/2b + app.d/2)上绘制波形,模拟了从两个狭缝发出的波的传播。波峰和波谷使用不同的颜色(peakColortroughColor)和线型(实线和虚线)来区分。

通过执行这段代码,我们可以观察到光波通过双缝后在屏幕上形成的干涉图样,包括干涉条纹的形成过程,从而深入理解双缝干涉现象。接下来,我们将讨论如何在仿真中模拟相长线与相消线的干涉效果。

(3)相长线与相消线

在双缝干涉的仿真中,相长线与相消线是干涉图样中的关键特征。相长线发生在两束光波的相位差为偶数倍的( \pi )时,导致光强加强;相消线则发生在相位差为奇数倍的( \pi )时,导致光强减弱。相位差通常反映为光程差,即:光程差为半波长的偶数倍时相长,为奇数倍是相消。数学上,距离之差围成的图形被称为双曲线,那么,相长线的方程可以表示为:

y 2 k 2 λ 2 4 − x 2 d 2 4 − k 2 λ 2 4 = 1 \frac{y^2}{\frac{k^2\lambda^2}{4}} - \frac{x^2}{\frac{d^2}{4}-\frac{k^2\lambda^2}{4}} = 1 4k2λ2y24d24k2λ2x2=1

相应的,相消线的方程可以表示为:

y 2 ( 2 k + 1 ) 2 λ 2 16 − x 2 d 2 4 − ( 2 k + 1 ) 2 λ 2 16 = 1 \frac{y^2}{\frac{(2k+1)^2\lambda^2}{16}} - \frac{x^2}{\frac{d^2}{4}-\frac{(2k+1)^2\lambda^2}{16}} = 1 16(2k+1)2λ2y24d216(2k+1)2λ2x2=1

上面两个式子中的(k) 取正整数。

下面是实现相长线与相消线模拟的关键代码段:

% 定时器回调函数中处理干涉条纹的逻辑
function SimFcn(app,~,~)
    % 之前的代码逻辑...
    
    InterferenceFringes = -1;
    if app.Constructive && app.Destructive
        InterferenceFringes = 2;
    elseif app.Constructive && ~app.Destructive
        InterferenceFringes = 1;
    elseif ~app.Constructive && app.Destructive
        InterferenceFringes = 0;
    end

    % 根据干涉类型调用绘制函数
    wave(app, app.WavePeak, app.WaveTrough, InterferenceFringes, app.flag);
end

% 绘制波形和干涉条纹的函数 wave
function wave(app, WavePeak, WaveTrough, InterferenceFringes, flag)
    % 之前的代码逻辑...
    
    if InterferenceFringes >= 0
        app.Interference(InterferenceFringes);
    end
end

% 实现干涉条纹绘制的函数 Interference
function Interference(app,flag,k)
    if flag == 1
        % 相长线
        fplot(app.UIAxes, @(x) sqrt(k^2*app.Lambda^2/(app.d^2-k^2*app.Lambda^2)*x.^2 + k^2*app.Lambda^2/4), [0, app.D], 'k');
        fplot(app.UIAxes, @(x) -sqrt(k^2*app.Lambda^2/(app.d^2-k^2*app.Lambda^2)*x.^2 + k^2*app.Lambda^2/4), [0, app.D], 'k');
    end
    if flag == 2
        % 相长线
        fplot(app.UIAxes, @(x) sqrt(k^2*app.Lambda^2/(app.d^2-k^2*app.Lambda^2)*x.^2 + k^2*app.Lambda^2/4), [0, app.D], 'k');
        fplot(app.UIAxes, @(x) -sqrt(k^2*app.Lambda^2/(app.d^2-k^2*app.Lambda^2)*x.^2 + k^2*app.Lambda^2/4), [0, app.D], 'k');

        % 相消线
        fplot(app.UIAxes, @(x) sqrt((2*k+1)^2*app.Lambda^2/(4*app.d^2-(2*k+1)^2*app.Lambda^2)*x.^2 + (2*k+1)^2*app.Lambda^2/16), [0, app.D], 'c');
        fplot(app.UIAxes, @(x) -sqrt((2*k+1)^2*app.Lambda^2/(4*app.d^2-(2*k+1)^2*app.Lambda^2)*x.^2 + (2*k+1)^2*app.Lambda^2/16), [0, app.D], 'c');
    end
    if flag == 0
        % 相消线
        fplot(app.UIAxes, @(x) sqrt((2*k+1)^2*app.Lambda^2/(4*app.d^2-(2*k+1)^2*app.Lambda^2)*x.^2 + (2*k+1)^2*app.Lambda^2/16), [0, app.D], 'c');
        fplot(app.UIAxes, @(x) -sqrt((2*k+1)^2*app.Lambda^2/(4*app.d^2-(2*k+1)^2*app.Lambda^2)*x.^2 + (2*k+1)^2*app.Lambda^2/16), [0, app.D], 'c');
    end
end

在这段代码中,SimFcn函数通过检查app.Constructiveapp.Destructive变量来确定需要绘制的干涉类型,然后调用wave函数来更新干涉图样。wave函数根据干涉类型参数InterferenceFringes,进一步调用Interference函数绘制相长线和相消线。

Interference函数中,根据type参数来决定绘制相长线、相消线或两者兼有。具体的绘图逻辑可以通过计算干涉条纹的位置和光强分布来实现,从而在GUI中呈现出清晰的干涉图样。这种方法准确地模拟了双缝干涉实验中的物理现象,使得仿真工具能够有效地用于教学和研究目的。

(4)实现定时器控制动态传播

在仿真应用中,定时器的作用是控制光波传播的动态模拟。我们通过定时器定期更新界面上的波形,来模拟光的动态传播过程。定时器确保了在每个时间间隔内,光波的状态都能得到更新,并且在GUI上呈现出来。

定时器的实现已经嵌入在仿真代码中,具体如下:

% 定时器回调函数,用于模拟光波传播
function SimFcn(app,~,~)
    % 获取CheckBoxTree_2的选中状态
    checkedNodes_2 = app.Tree_2.CheckedNodes;
    % 默认设置为不显示波峰和波谷
    app.WavePeak = false;
    app.WaveTrough = false;

    % 遍历选中的节点,决定是否显示波峰和波谷
    for i = 1:length(checkedNodes_2)
        if isequal(checkedNodes_2(i).Text, '波峰:红色')
            app.WavePeak = true;
        elseif isequal(checkedNodes_2(i).Text, '波谷:蓝色')
            app.WaveTrough = true;
        end
    end

    % 获取CheckBoxTree的选中状态
    checkedNodes = app.Tree.CheckedNodes;
    % 默认设置为不显示相长线与相消线
    app.Constructive = false;
    app.Destructive = false;

    % 遍历选中的节点,决定是否显示波峰和波谷
    for i = 1:length(checkedNodes)
        if isequal(checkedNodes(i).Text, '相长线')
            app.Constructive = true;
        elseif isequal(checkedNodes(i).Text, '相消线')
            app.Destructive = true;
        end
    end

    InterferenceFringes = -1;
    if app.Constructive && app.Destructive
        InterferenceFringes = 2;
    elseif app.Constructive && ~app.Destructive
        InterferenceFringes = 1;
    elseif ~app.Constructive && app.Destructive
        InterferenceFringes = 0;
    end

    % 调用wave函数,传入波峰和波谷的显示状态
    if app.t < app.N
        wave(app,app.WavePeak, app.WaveTrough,InterferenceFringes, 0);
    elseif app.t >= app.N
        wave(app,app.WavePeak, app.WaveTrough,InterferenceFringes, 1);
        app.T = app.T + 1;
    end
    app.t = app.t + 1;

end

% 用于启动和停止定时器的播放控制按钮
function Button_2Pushed(app, event)
    app.Button.Enable = "on";
    app.Slider.Enable = "off";
    if app.Button_2.Text == "播放"
        if isempty(app.Sim) || ~isvalid(app.Sim)
            % 创建或重置定时器
            app.Sim = timer('ExecutionMode', 'fixedRate', 'Period', app.frame, 'TimerFcn', @app.SimFcn);
        end
        start(app.Sim);
        app.Button_2.Text = "暂停";
    elseif app.Button_2.Text == "暂停"
        stop(app.Sim);
        app.Button_2.Text = "播放";
    end
end

% 用于重置播放动画的控制按钮
function ButtonPushed(app, event)
    app.Slider.Enable = "on";
    stop(app.Sim);
    delete(app.Sim);
    cla(app.UIAxes);
    app.Button_2.Text = "播放";
    app.Button.Enable = "off";
    app.Slider.Enable = "on";
    app.t = 1;
    app.T = 1;
end

在这段代码中,定时器app.SimstartupFcn函数中被初始化,并设置了执行模式、周期和回调函数。回调函数SimFcn负责根据定时器的触发更新光波传播的动态画面。播放控制按钮通过Button_2Pushed函数来控制定时器的启动和停止,从而控制仿真的进行。

这种设计使得用户可以通过界面上的按钮来控制仿真的播放和暂停,实现了对光波传播过程的动态观察和分析。

2、干涉图像仿真

(1)绘制干涉图像

仿真中的干涉图像绘制是通过计算双缝干涉的光程差来实现的。这一过程详细考虑了从双缝到观察屏幕上各点的光程差,进而计算出相应位置的光强度,形成干涉图样。

ference函数中,首先定义了波长(Lambda)、双缝间距(d)、屏幕距离(D)以及观察范围(xRange)。然后,计算每一点的光程差(Phi),并根据光程差计算出该点的光强度(B)。这些光强度值最终被映射为图像上的亮度,形成了干涉条纹。

function ference(app)
    % 获取参数,保持使用国际单位制
    Lambda = app.Lambda;
    d = app.d; 
    Z = app.D; 
    xRange = app.x; % 条纹的横坐标范围

    % 计算光程差
    Nx = 225;
    xs = linspace(-xRange, xRange, Nx);
    r1 = sqrt((xs - d/2).^2 + Z^2);
    r2 = sqrt((xs + d/2).^2 + Z^2);
    Phi = 2 * pi * (r2 - r1) / Lambda;

    % 绘制图像
    B = 4 * cos(Phi / 2).^2;
    Br = (B / max(B)) * 255; % 计算光强度

    % 创建图像数据并绘制条纹图像
    figure;
    subplot(2, 1, 1);
    image(xs, 0, Br);
    colormap('hot');
    colorbar;
    axis off;

    % 绘制光强图
    subplot(2, 1, 2);
    plot(xs, Br, 'r');
    xlabel('Position (m)');
    ylabel('Intensity');
    axis tight;
end
(2)设置颜色

色彩的设置在干涉图像的表达中至关重要,它有助于区分不同光强度的区域。通过色彩的变化,可以清晰地区分干涉条纹中的明暗区域,使得干涉图样更加易于理解和分析。

在仿真程序中,颜色的设置是通过调整色彩映射函数实现的,如使用colormap函数来定义不同光强度的颜色表示:

function [R,G,B] = wave2color(app,lambda)
    a = lambda;
    if a <= 439
        R = -(a-440)/(440-380);
        G = 0;
        B = 1;
    elseif a<=489
        R = 0;
        G = (a-440)/(490-440);
        B = 1;
    elseif a<=509
        R = 0;
        G = 1;
        B = -(a-510)/(510-490);
    elseif a<=579
        R = (a-510)/(580-510);
        G = 1;
        B = 0;
    elseif a<=644
        R = 1;
        G = -(a-645)/(645-580);
        B = 0;
    elseif a<=780
        R = 1;
        G = 0;
        B = 0;
    else
        R = 1;
        G = 1;
        B = 1;
    end
end

通过这种方式,干涉图像中的每一点都可以根据其光强度或波长映射到相应的颜色,从而实现丰富的视觉效果和直观的科学解释。

3、设置说明介绍

为了更好地理解双缝干涉仿真程序的操作和参数设置,我们可以在GUI中加入一个上下文菜单(context menu),为用户提供详细的说明和指导。这个上下文菜单可以包含有关仿真参数、控件功能以及如何操作仿真界面的信息。

下面是设置上下文菜单并提供说明信息的代码:

function MenuSelected(app, event)
    message = [
        "杨氏双缝干涉仿真说明:\n", ...
        "参数(单位:微米):\n", ...
        "  - $\lambda$: 波长\n", ...
        "  - $d$: 双缝间距\n", ...
        "  - $D$: 屏距\n", ...
        "  - $x$: 到屏中心距离\n\n", ...
        "控件说明:\n", ...
        "  - 播放:开始或暂停仿真。\n", ...
        "  - 重置:清除仿真画面并重置参数。\n", ...
        "  - 仿真:根据当前参数显示干涉效果。"
    ];
    uialert(app.UIFigure, message, "仿真说明", "Icon", 'info', 'Interpreter','latex');
end

在这段代码中,MenuSelected函数定义了当上下文菜单被选中时展示的信息。这些信息通过uialert函数在一个对话框中显示给用户,提供了关于仿真程序的详细说明。这种方法不仅增强了用户界面的友好性,也使得非专业用户能够更容易地理解和操作仿真程序。

四、附录

最后,附上本文的源代码:

classdef DoubleSlitInterference < matlab.apps.AppBase

    % Properties that correspond to app components
    properties (Access = public)
        UIFigure         matlab.ui.Figure
        Slider           matlab.ui.control.Slider
        Label_2          matlab.ui.control.Label
        Panel            matlab.ui.container.Panel
        Button_3         matlab.ui.control.Button
        Button_2         matlab.ui.control.Button
        Button           matlab.ui.control.Button
        Tree_2           matlab.ui.container.CheckBoxTree
        Node_4           matlab.ui.container.TreeNode
        Node_5           matlab.ui.container.TreeNode
        Node_6           matlab.ui.container.TreeNode
        Tree             matlab.ui.container.CheckBoxTree
        Node             matlab.ui.container.TreeNode
        Node_2           matlab.ui.container.TreeNode
        Node_3           matlab.ui.container.TreeNode
        Panel_2          matlab.ui.container.Panel
        xEditField       matlab.ui.control.NumericEditField
        xEditFieldLabel  matlab.ui.control.Label
        DEditField       matlab.ui.control.NumericEditField
        DEditFieldLabel  matlab.ui.control.Label
        dEditField       matlab.ui.control.NumericEditField
        dEditFieldLabel  matlab.ui.control.Label
        EditField        matlab.ui.control.NumericEditField
        EditFieldLabel   matlab.ui.control.Label
        Label            matlab.ui.control.Label
        UIAxes           matlab.ui.control.UIAxes
        ContextMenu      matlab.ui.container.ContextMenu
        Menu             matlab.ui.container.Menu
    end

    
    properties (Access = public)
        Lambda % 波长
        d % 双缝间距
        D % 屏距
        x % P到屏中心的距离
        frame % 动画速度
        Sim % 动画计时器
        t % 运行时间(波数)
        colorState = true % 初始颜色状态,true表示红色为波峰,蓝色为波谷
        WavePeak % 波峰选择
        WaveTrough % 波谷选择
        N % 到双缝所需波数
        P % 到显示处所需波数
        T % 双缝之后的运行时间(波数)
        Constructive % 相长线
        Destructive % 相消线
    end

    methods (Access = public)

        function SimFcn(app,~,~)
            % 获取CheckBoxTree_2的选中状态
            checkedNodes_2 = app.Tree_2.CheckedNodes;
            % 默认设置为不显示波峰和波谷
            app.WavePeak = false;
            app.WaveTrough = false;

            % 遍历选中的节点,决定是否显示波峰和波谷
            for i = 1:length(checkedNodes_2)
                if isequal(checkedNodes_2(i).Text, '波峰:红色')
                    app.WavePeak = true;
                elseif isequal(checkedNodes_2(i).Text, '波谷:蓝色')
                    app.WaveTrough = true;
                end
            end

            % 获取CheckBoxTree的选中状态
            checkedNodes = app.Tree.CheckedNodes;
            % 默认设置为不显示相长线与相消线
            app.Constructive = false;
            app.Destructive = false;

            % 遍历选中的节点,决定是否显示波峰和波谷
            for i = 1:length(checkedNodes)
                if isequal(checkedNodes(i).Text, '相长线')
                    app.Constructive = true;
                elseif isequal(checkedNodes(i).Text, '相消线')
                    app.Destructive = true;
                end
            end

            InterferenceFringes = -1;
            if app.Constructive && app.Destructive
                InterferenceFringes = 2;
            elseif app.Constructive && ~app.Destructive
                InterferenceFringes = 1;
            elseif ~app.Constructive && app.Destructive
                InterferenceFringes = 0;
            end

            % 调用wave函数,传入波峰和波谷的显示状态
            if app.t < app.N
                wave(app,app.WavePeak, app.WaveTrough,InterferenceFringes, 0);
            elseif app.t >= app.N
                wave(app,app.WavePeak, app.WaveTrough,InterferenceFringes, 1);
                app.T = app.T + 1;
            end
            app.t = app.t + 1;

        end

        function wave(app,WavePeak,WaveTrough,InterferenceFringes,flag)
            
            hold(app.UIAxes, 'on'); % 保持当前图形,允许在上面绘制更多图形元素

            % 清除上一次的波峰和波谷,以避免画面过于拥挤
            cla(app.UIAxes);
            
            plot(app.UIAxes, [0, 0], [-app.d/2, app.d/2], 'k', 'LineWidth', 2);
            plot(app.UIAxes, [0, 0], [app.d/2 + 0.2, 5], 'k', 'LineWidth', 2);
            plot(app.UIAxes, [0, 0], [-app.d/2 - 0.2, -5], 'k', 'LineWidth', 2);

            % 如果colorState字段不存在,则初始化它
            if ~isfield(app, 'colorState')
                app.colorState = true; % true 表示本次绘制波峰为红色,波谷为蓝色
            end

            % 根据colorState的值选择颜色
            if app.colorState
                peakColor = 'r';
                troughColor = 'b';
            else
                peakColor = 'b';
                troughColor = 'r';
            end

            % 绘制波峰和波谷
            if flag == 0
                WavePT(app,app.t,WavePeak,WaveTrough,peakColor,troughColor,-5,0,-5,0,-5,5)
            elseif flag == 1
                WavePT(app,app.t,WavePeak,WaveTrough,peakColor,troughColor,-5,0,-5,0,-5,5)
                WavePT(app,app.T,WavePeak,WaveTrough,peakColor,troughColor,0,app.d/2,0,app.D,-5,5)
                WavePT(app,app.T,WavePeak,WaveTrough,peakColor,troughColor,0,-app.d/2,0,app.D,-5,5)
                if InterferenceFringes == 0
                    app.Interference(0,0);
                    app.Interference(0,1);
                    app.Interference(0,2);
                    app.Interference(0,3);
                    app.Interference(0,4);
                elseif InterferenceFringes == 1
                    app.Interference(1,0);
                    app.Interference(1,1);
                    app.Interference(1,2);
                    app.Interference(1,3);
                    app.Interference(1,4);
                elseif InterferenceFringes == 2
                    app.Interference(2,0);
                    app.Interference(2,1);
                    app.Interference(2,2);
                    app.Interference(2,3);
                    app.Interference(2,4);
                end
            end
            % 切换颜色状态
            app.colorState = ~app.colorState;

            drawnow;
        end

        function WavePT(app,t,WavePeak,WaveTrough,peakColor,troughColor,a,b,X1,X2,Y1,Y2)
            hold(app.UIAxes, 'on');
            for n = t:-2:1
                wavePeak = n * app.Lambda / 2;
                waveTrough = (n-1) * app.Lambda / 2;

                % 绘制波峰
                if WavePeak
                    fimplicit(app.UIAxes,@(X,Y) (X-a).^2 + (Y-b).^2 - wavePeak^2, [X1,X2,Y1,Y2], 'Color', peakColor);
                end

                if WaveTrough
                    % 绘制波谷
                    fimplicit(app.UIAxes,@(X,Y) (X-a).^2 + (Y-b).^2 - waveTrough^2, [X1,X2,Y1,Y2], 'Color', troughColor);
                end
            end
        end
        
        function Interference(app,flag,k)
            if flag == 1
                % 相长线
                fplot(app.UIAxes, @(x) sqrt(k^2*app.Lambda^2/(app.d^2-k^2*app.Lambda^2)*x.^2 + k^2*app.Lambda^2/4), [0, app.D], 'k');
                fplot(app.UIAxes, @(x) -sqrt(k^2*app.Lambda^2/(app.d^2-k^2*app.Lambda^2)*x.^2 + k^2*app.Lambda^2/4), [0, app.D], 'k');
            end
            if flag == 2
                % 相长线
                fplot(app.UIAxes, @(x) sqrt(k^2*app.Lambda^2/(app.d^2-k^2*app.Lambda^2)*x.^2 + k^2*app.Lambda^2/4), [0, app.D], 'k');
                fplot(app.UIAxes, @(x) -sqrt(k^2*app.Lambda^2/(app.d^2-k^2*app.Lambda^2)*x.^2 + k^2*app.Lambda^2/4), [0, app.D], 'k');

                % 相消线
                fplot(app.UIAxes, @(x) sqrt((2*k+1)^2*app.Lambda^2/(4*app.d^2-(2*k+1)^2*app.Lambda^2)*x.^2 + (2*k+1)^2*app.Lambda^2/16), [0, app.D], 'c');
                fplot(app.UIAxes, @(x) -sqrt((2*k+1)^2*app.Lambda^2/(4*app.d^2-(2*k+1)^2*app.Lambda^2)*x.^2 + (2*k+1)^2*app.Lambda^2/16), [0, app.D], 'c');
            end
            if flag == 0
                % 相消线
                fplot(app.UIAxes, @(x) sqrt((2*k+1)^2*app.Lambda^2/(4*app.d^2-(2*k+1)^2*app.Lambda^2)*x.^2 + (2*k+1)^2*app.Lambda^2/16), [0, app.D], 'c');
                fplot(app.UIAxes, @(x) -sqrt((2*k+1)^2*app.Lambda^2/(4*app.d^2-(2*k+1)^2*app.Lambda^2)*x.^2 + (2*k+1)^2*app.Lambda^2/16), [0, app.D], 'c');
            end
        end

        function ference(app)
            % 获取参数,保持使用国际单位制
            Lambda = app.Lambda;
            d = app.d; 
            Z = app.D; 
            xRange = app.x; % 条纹的横坐标范围 [-x, x]

            % 计算光程差
            Nx = 225;
            xs = linspace(-xRange, xRange, Nx);
            ys = xRange;
            r1 = sqrt((xs - d/2).^2 + Z^2);
            r2 = sqrt((xs + d/2).^2 + Z^2);
            Phi = 2 * pi * (r2 - r1) / Lambda;

            % 绘制图像
            B = 4 * cos(Phi / 2).^2;
            NCLevels = 255;
            Br = (B / max(B)) * NCLevels; % 直接计算水平方向的光强度            

            % 创建图像数据,为形成竖直条纹,沿Y轴复制Br
            % imgData = repmat(Br, [100, 1]);           % Y轴复制100倍以形成竖直条纹

            % 创建新窗口并绘制条纹图像
            figure;
            subplot(2, 1, 1);
            image(xs,ys,Br);
            c = linspace(0,1,64)';
            [R,G,B] = app.wave2color(Lambda*1000);
            colormap([c*R,c*G,c*B]);
            % imagesc([-xRange, xRange], [0, 4], imgData);
            % colormap([linspace(0, 1, NCLevels)', zeros(NCLevels, 1), zeros(NCLevels, 1)]);  % 红色映射
            axis off;                                 % 不显示坐标轴

            % 绘制光强图
            subplot(2, 1, 2);
            plot(xs, Br, 'r');                         % 使用红色线表示光强
            xlabel('Position (m)');
            ylabel('Intensity');
            axis tight;
        end

        
        function [R,G,B] = wave2color(app,lambda)
            a = lambda;
            if a <= 439
                R = -(a-440)/(440-380);
                G = 0;
                B = 1;
            elseif a<=489
                R = 0;
                G = (a-440)/(490-440);
                B = 1;
            elseif a<=509
                R = 0;
                G = 1;
                B = -(a-510)/(510-490);
            elseif a<=579
                R = (a-510)/(580-510);
                G = 1;
                B = 0;
            elseif a<=644
                R = 1;
                G = -(a-645)/(645-580);
                B = 0;
            elseif a<=780
                R = 1;
                G = 0;
                B = 0;
            else
                R = 1;
                G = 1;
                B = 1;
            end
        end
    end
    

    % Callbacks that handle component events
    methods (Access = private)

        % Code that executes after component creation
        function startupFcn(app)
            app.Lambda = app.EditField.Value;
            app.d = app.dEditField.Value;
            app.D = app.DEditField.Value;
            app.x = app.xEditField.Value;
            app.frame = app.Slider.Value;
            
            
            app.N = ceil(2*sqrt(25+app.d^2/4)/app.Lambda); % 到双缝所需波数
            app.P = ceil(2*sqrt(app.x^2+app.d^2/4)/app.Lambda); % 到显示处所需波数

            % 设置坐标轴范围
            xlim(app.UIAxes, [-5, 10]);
            ylim(app.UIAxes, [-5, 5]);
            app.t = 1;
            app.T = 1;

            app.Button.Enable = "off";
        end

        % Button pushed function: Button_2
        function Button_2Pushed(app, event)
            app.Button.Enable = "on";
            app.Slider.Enable = "off";
            if app.Button_2.Text == "播放"
                if isempty(app.Sim) || ~isvalid(app.Sim)
                    % 创建或重置定时器
                    app.Sim = timer('ExecutionMode', 'fixedRate', 'Period', app.frame, 'TimerFcn', @app.SimFcn);
                end
                start(app.Sim);
                app.Button_2.Text = "暂停";
            elseif app.Button_2.Text == "暂停"
                stop(app.Sim);
                app.Button_2.Text = "播放";
            end
        end

        % Button pushed function: Button
        function ButtonPushed(app, event)
            app.Slider.Enable = "on";
            stop(app.Sim);
            delete(app.Sim);
            cla(app.UIAxes);
            app.Button_2.Text = "播放";
            app.Button.Enable = "off";
            app.Slider.Enable = "on";
            app.t = 1;
            app.T = 1;
        end

        % Button pushed function: Button_3
        function Button_3Pushed(app, event)
            app.ference;
        end

        % Value changed function: EditField
        function EditFieldValueChanged(app, event)
            value = app.EditField.Value;
            app.Lambda = value;
        end

        % Value changed function: dEditField
        function dEditFieldValueChanged(app, event)
            value = app.dEditField.Value;
            app.d = value;
        end

        % Value changed function: DEditField
        function DEditFieldValueChanged(app, event)
            value = app.DEditField.Value;
            app.D = value;
        end

        % Value changed function: xEditField
        function xEditFieldValueChanged(app, event)
            value = app.xEditField.Value;
            app.x = value;
        end

        % Menu selected function: Menu
        function MenuSelected(app, event)
            message = [
                "杨氏双缝干涉仿真说明:", ...
                "参数(单位:$\mu$m):$\lambda$ (波长), $d$ (双缝间距), $D$ (屏距), $x$ (到屏中心距离).", ...
                "控件:", ...
                "- 播放:开始/暂停仿真。", ...
                "- 重置:清除仿真画面。", ...
                "- 仿真:显示干涉效果。"
                ];
            uialert(app.UIFigure, message, "关于", "Icon", 'info', 'Interpreter','latex');
        end
    end

    % Component initialization
    methods (Access = private)

        % Create UIFigure and components
        function createComponents(app)

            % Create UIFigure and hide until all components are created
            app.UIFigure = uifigure('Visible', 'off');
            app.UIFigure.Position = [100 100 640 480];
            app.UIFigure.Name = 'MATLAB App';

            % Create UIAxes
            app.UIAxes = uiaxes(app.UIFigure);
            app.UIAxes.Position = [30 90 360 240];

            % Create Label
            app.Label = uilabel(app.UIFigure);
            app.Label.BackgroundColor = [0 0 1];
            app.Label.HorizontalAlignment = 'center';
            app.Label.FontName = '黑体';
            app.Label.FontSize = 36;
            app.Label.FontWeight = 'bold';
            app.Label.FontColor = [1 1 1];
            app.Label.Position = [0 420 640 60];
            app.Label.Text = '杨氏双缝干涉仿真动画';

            % Create Panel_2
            app.Panel_2 = uipanel(app.UIFigure);
            app.Panel_2.Title = '参数输入(单位:微米)';
            app.Panel_2.Position = [415 250 200 150];

            % Create EditFieldLabel
            app.EditFieldLabel = uilabel(app.Panel_2);
            app.EditFieldLabel.HorizontalAlignment = 'right';
            app.EditFieldLabel.Position = [20 98 25 22];
            app.EditFieldLabel.Text = '波长';

            % Create EditField
            app.EditField = uieditfield(app.Panel_2, 'numeric');
            app.EditField.Limits = [0.39 0.76];
            app.EditField.ValueChangedFcn = createCallbackFcn(app, @EditFieldValueChanged, true);
            app.EditField.HorizontalAlignment = 'center';
            app.EditField.Position = [60 99 120 20];
            app.EditField.Value = 0.6;

            % Create dEditFieldLabel
            app.dEditFieldLabel = uilabel(app.Panel_2);
            app.dEditFieldLabel.HorizontalAlignment = 'right';
            app.dEditFieldLabel.Position = [20 69 25 22];
            app.dEditFieldLabel.Text = 'd';

            % Create dEditField
            app.dEditField = uieditfield(app.Panel_2, 'numeric');
            app.dEditField.ValueChangedFcn = createCallbackFcn(app, @dEditFieldValueChanged, true);
            app.dEditField.HorizontalAlignment = 'center';
            app.dEditField.Position = [60 70 120 20];
            app.dEditField.Value = 3;

            % Create DEditFieldLabel
            app.DEditFieldLabel = uilabel(app.Panel_2);
            app.DEditFieldLabel.HorizontalAlignment = 'right';
            app.DEditFieldLabel.Position = [20 39 25 22];
            app.DEditFieldLabel.Text = 'D';

            % Create DEditField
            app.DEditField = uieditfield(app.Panel_2, 'numeric');
            app.DEditField.ValueChangedFcn = createCallbackFcn(app, @DEditFieldValueChanged, true);
            app.DEditField.HorizontalAlignment = 'center';
            app.DEditField.Position = [60 40 120 20];
            app.DEditField.Value = 10;

            % Create xEditFieldLabel
            app.xEditFieldLabel = uilabel(app.Panel_2);
            app.xEditFieldLabel.HorizontalAlignment = 'right';
            app.xEditFieldLabel.Position = [20 9 25 22];
            app.xEditFieldLabel.Text = 'x';

            % Create xEditField
            app.xEditField = uieditfield(app.Panel_2, 'numeric');
            app.xEditField.ValueChangedFcn = createCallbackFcn(app, @xEditFieldValueChanged, true);
            app.xEditField.HorizontalAlignment = 'center';
            app.xEditField.Position = [60 10 120 20];
            app.xEditField.Value = 5;

            % Create Panel
            app.Panel = uipanel(app.UIFigure);
            app.Panel.Title = '双缝干涉动画';
            app.Panel.Position = [415 65 200 170];

            % Create Tree
            app.Tree = uitree(app.Panel, 'checkbox');
            app.Tree.FontSize = 5;
            app.Tree.Position = [0 0 125 75];

            % Create Node
            app.Node = uitreenode(app.Tree);
            app.Node.Text = '干涉线';

            % Create Node_2
            app.Node_2 = uitreenode(app.Node);
            app.Node_2.Text = '相长线';

            % Create Node_3
            app.Node_3 = uitreenode(app.Node);
            app.Node_3.Text = '相消线';

            % Create Tree_2
            app.Tree_2 = uitree(app.Panel, 'checkbox');
            app.Tree_2.FontSize = 5;
            app.Tree_2.Position = [0 75 125 75];

            % Create Node_4
            app.Node_4 = uitreenode(app.Tree_2);
            app.Node_4.Text = '颜色选择';

            % Create Node_5
            app.Node_5 = uitreenode(app.Node_4);
            app.Node_5.Text = '波峰:红色';

            % Create Node_6
            app.Node_6 = uitreenode(app.Node_4);
            app.Node_6.Text = '波谷:蓝色';

            % Create Button
            app.Button = uibutton(app.Panel, 'push');
            app.Button.ButtonPushedFcn = createCallbackFcn(app, @ButtonPushed, true);
            app.Button.Position = [138 99 50 30];
            app.Button.Text = '重置';

            % Create Button_2
            app.Button_2 = uibutton(app.Panel, 'push');
            app.Button_2.ButtonPushedFcn = createCallbackFcn(app, @Button_2Pushed, true);
            app.Button_2.Position = [138 56 50 30];
            app.Button_2.Text = '播放';

            % Create Button_3
            app.Button_3 = uibutton(app.Panel, 'push');
            app.Button_3.ButtonPushedFcn = createCallbackFcn(app, @Button_3Pushed, true);
            app.Button_3.Position = [138 13 50 30];
            app.Button_3.Text = '仿真';

            % Create Label_2
            app.Label_2 = uilabel(app.UIFigure);
            app.Label_2.HorizontalAlignment = 'right';
            app.Label_2.Position = [415 31 53 22];
            app.Label_2.Text = '播放速度';

            % Create Slider
            app.Slider = uislider(app.UIFigure);
            app.Slider.Limits = [0 1];
            app.Slider.Position = [489 40 115 3];
            app.Slider.Value = 0.2;

            % Create ContextMenu
            app.ContextMenu = uicontextmenu(app.UIFigure);

            % Create Menu
            app.Menu = uimenu(app.ContextMenu);
            app.Menu.MenuSelectedFcn = createCallbackFcn(app, @MenuSelected, true);
            app.Menu.Text = '关于';
            
            % Assign app.ContextMenu
            app.UIFigure.ContextMenu = app.ContextMenu;

            % Show the figure after all components are created
            app.UIFigure.Visible = 'on';
        end
    end

    % App creation and deletion
    methods (Access = public)

        % Construct app
        function app = DoubleSlitInterference

            % Create UIFigure and components
            createComponents(app)

            % Register the app with App Designer
            registerApp(app, app.UIFigure)

            % Execute the startup function
            runStartupFcn(app, @startupFcn)

            if nargout == 0
                clear app
            end
        end

        % Code that executes before app deletion
        function delete(app)

            % Delete UIFigure when app is deleted
            delete(app.UIFigure)
        end
    end
end
ntextMenu);
            app.Menu.MenuSelectedFcn = createCallbackFcn(app, @MenuSelected, true);
            app.Menu.Text = '关于';
            
            % Assign app.ContextMenu
            app.UIFigure.ContextMenu = app.ContextMenu;

            % Show the figure after all components are created
            app.UIFigure.Visible = 'on';
        end
    end

    % App creation and deletion
    methods (Access = public)

        % Construct app
        function app = DoubleSlitInterference

            % Create UIFigure and components
            createComponents(app)

            % Register the app with App Designer
            registerApp(app, app.UIFigure)

            % Execute the startup function
            runStartupFcn(app, @startupFcn)

            if nargout == 0
                clear app
            end
        end

        % Code that executes before app deletion
        function delete(app)

            % Delete UIFigure when app is deleted
            delete(app.UIFigure)
        end
    end
end
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值