classdef HeatEquationSolver < matlab.apps.AppBase
% 属性
properties (Access = public)
UIFigure matlab.ui.Figure
TabGroup matlab.ui.container.TabGroup
% 设置选项卡组件
SettingsTab matlab.ui.container.Tab
SolveButton matlab.ui.control.Button
StatusLabel matlab.ui.control.Label
% 空间区域设置
DomainLabel matlab.ui.control.Label
LLabel matlab.ui.control.Label
LEditField matlab.ui.control.NumericEditField
NLabel matlab.ui.control.Label
NEditField matlab.ui.control.NumericEditField
% 时间设置
TimeLabel matlab.ui.control.Label
TfinalLabel matlab.ui.control.Label
TfinalEditField matlab.ui.control.NumericEditField
dtLabel matlab.ui.control.Label
dtEditField matlab.ui.control.NumericEditField
% 物理参数设置
PhysicalParamsLabel matlab.ui.control.Label
AlphaLabel matlab.ui.control.Label
AlphaEditField matlab.ui.control.NumericEditField
% 初始条件和边界条件设置
ICLabel matlab.ui.control.Label
ICDropDown matlab.ui.control.DropDown
ICPreviewPanel matlab.ui.container.Panel
ICPreviewAxes % 修改为通用类型,兼容axes和uiaxes
BCLabel matlab.ui.control.Label
BCDropDown matlab.ui.control.DropDown
% 数值方法设置
MethodLabel matlab.ui.control.Label
SpectralMethodDropDown matlab.ui.control.DropDown
CompareWithFDMCheckBox matlab.ui.control.CheckBox
% 可视化选项卡组件
VisualizationTab matlab.ui.container.Tab
SolutionPlot % 修改为通用类型,兼容axes和uiaxes
TimeSlider matlab.ui.control.Slider
CurrentTimeLabel matlab.ui.control.Label
PlayButton matlab.ui.control.Button
AnimationSpeedLabel matlab.ui.control.Label
AnimationSpeedDropDown matlab.ui.control.DropDown
% 误差分析选项卡组件
ErrorAnalysisTab matlab.ui.container.Tab
ErrorPlot % 修改为通用类型,兼容axes和uiaxes
PerformanceMetricsTextArea matlab.ui.control.TextArea
PerfBarAxes % 修改为通用类型,兼容axes和uiaxes
% 帮助选项卡
HelpTab matlab.ui.container.Tab
end
% 内部属性 - 存储计算结果
properties (Access = private)
x % 空间网格点
t % 时间点
u_spectral % 谱方法解
u_fdm % 有限差分法解(如果选择比较)
u_exact % 精确解(如果有)
errorData % 误差数据
solveTime % 求解时间
fdmSolveTime % 有限差分法求解时间
AnimationTimer % 动画定时器
% 计算参数
L % 计算域长度
N % 空间离散点数
Tfinal % 最终时间
dt % 时间步长
alpha % 热扩散系数
% 状态标志
isSolved % 是否已求解
end
methods (Access = private)
% 初始条件函数
function u0 = getInitialCondition(app, x)
switch app.ICDropDown.Value
case '高斯脉冲'
% 高斯脉冲
%单个高斯脉冲重复排列,形成周期性的信号
%将脉冲置于计算域的中心,可以保证在周期性边界条件下,脉冲的左右部分对称,避免因边界不连续导致的数值误差。
%L/20意味着脉冲的宽度相对于整个计算域L来说较小,但又不至于太小导致数值离散化困难。
%在显式时间积分方法中,时间步长dt通常受CFL(Courant-Friedrichs-Lewy)条件限制。
% 较小的σ可能导致空间梯度更大,需要更小的时间步长来保持稳定性。选择σ=L/20可能在保证一定精度的同时,允许使用较大的dt,提高计算效率。
sigma = app.L/20;
u0 = exp(-(x-app.L/2).^2/(2*sigma^2));
case '阶跃函数'
% 阶跃函数
u0 = zeros(size(x));
u0(x >= app.L/4 & x <= 3*app.L/4) = 1;
%
case '正弦函数'
% 正弦函数
u0 = sin(pi*x/app.L);
case '自定义'
% 这里可以添加用户输入自定义初始条件的功能
u0 = sin(pi*x/app.L); % 默认为正弦函数
end
%高斯脉冲:在计算域中心 (L/2) 生成高斯分布,标准差 σ = L/20。
%阶跃函数:在区间 [L/4, 3L/4] 内设为1,其他区域为0。
%正弦函数:生成波长与计算域长度匹配的正弦波,生成正弦波:`u0 = sin(πx/L)`,波长为`2L`。
end
% 更新初始条件预览图
function updateICPreview(app)
% 获取当前计算域参数
L = app.LEditField.Value;
N = 100; % 预览用的固定点数
% 创建均匀网格
dx = L/N;
x = (0:N-1)'*dx;
% 临时设置app.L以便于getInitialCondition使用
app.L = L;
% 获取初始条件
u0 = app.getInitialCondition(x);
% 绘制初始条件预览
cla(app.ICPreviewAxes);
plot(app.ICPreviewAxes, x, u0, 'b-', 'LineWidth', 1.5);
xlabel(app.ICPreviewAxes, 'x');
ylabel(app.ICPreviewAxes, 'u₀');
title(app.ICPreviewAxes, '初始条件');
grid(app.ICPreviewAxes, 'on');
app.ICPreviewAxes.Box = 'on';
end
% 使用傅里叶谱方法求解函数
function solve_fourier_spectral(app)
tic; % 开始计时
% 获取计算参数
app.L = app.LEditField.Value;
app.N = app.NEditField.Value;
app.Tfinal = app.TfinalEditField.Value;
app.dt = app.dtEditField.Value;
app.alpha = app.AlphaEditField.Value;
% 创建空间网格
dx = app.L/app.N;
app.x = (0:app.N-1)'*dx;
% 创建时间点
Nt = ceil(app.Tfinal/app.dt);
app.dt = app.Tfinal/Nt; % 调整dt以匹配Tfinal
app.t = (0:Nt)'*app.dt;
% 初始条件
u0 = app.getInitialCondition(app.x);
% 初始化解
app.u_spectral = zeros(app.N, Nt+1);
app.u_spectral(:,1) = u0;
% 计算波数
if mod(app.N, 2) == 0
k = 2*pi/app.L * [0:app.N/2-1, 0, -app.N/2+1:-1]';
else
k = 2*pi/app.L * [0:(app.N-1)/2, -(app.N-1)/2:-1]';
end
% 傅里叶变换初始条件
u_hat = fft(u0);
% 时间推进
for n = 1:Nt
% 热方程的谱解:du_hat/dt = -alpha*k^2*u_hat
% 使用精确积分因子求解
u_hat = u_hat .* exp(-app.alpha*k.^2*app.dt);
% 反变换回物理空间
app.u_spectral(:,n+1) = real(ifft(u_hat));
end
% 记录求解时间
app.solveTime = toc;
% 设置状态标志
app.isSolved = true;
% 如果选择同时使用有限差分法,则计算FDM解
if app.CompareWithFDMCheckBox.Value
app.solve_fdm();
end
% 更新界面
app.updateVisualization();
app.updateErrorAnalysis();
end
% 使用有限差分法求解函数
function solve_fdm(app)
tic; % 开始计时
% 获取计算参数
app.L = app.LEditField.Value;
app.N = app.NEditField.Value;
app.Tfinal = app.TfinalEditField.Value;
app.dt = app.dtEditField.Value;
app.alpha = app.AlphaEditField.Value;
% 创建均匀网格
dx = app.L/app.N;
app.x = (0:app.N-1)'*dx;
% 创建时间点
Nt = ceil(app.Tfinal/app.dt);
app.dt = app.Tfinal/Nt; % 调整dt以匹配Tfinal
app.t = (0:Nt)'*app.dt;
% 初始条件
u0 = app.getInitialCondition(app.x);
% 初始化解
if app.CompareWithFDMCheckBox.Value && strcmp(app.SpectralMethodDropDown.Value, '有限差分法')
% 如果当前是FDM作为主方法,且需要比较,那么存储在u_fdm中
app.u_fdm = zeros(app.N, length(app.t));
app.u_fdm(:,1) = u0;
% 并且调用傅里叶谱方法作为比较
app.solve_fourier_for_comparison();
% 最后将FDM结果复制到u_spectral以便统一显示
app.u_spectral = app.u_fdm;
else
% 如果是独立求解或者作为比较方法,那么存储在u_fdm中
app.u_spectral = zeros(app.N, length(app.t));
app.u_spectral(:,1) = u0;
if app.CompareWithFDMCheckBox.Value
% 如果是作为比较方法,也初始化u_fdm
app.u_fdm = zeros(app.N, length(app.t));
app.u_fdm(:,1) = u0;
end
end
% 计算稳定时间步长
dt_stable = 0.5*dx^2/app.alpha;
% 检查时间步长是否稳定
dt_fdm = min(app.dt, dt_stable);
r = app.alpha * dt_fdm / dx^2; % 扩散数
% 构造有限差分矩阵(使用中心差分)
e = ones(app.N, 1);
A = spdiags([e -2*e e], [-1 0 1], app.N, app.N);
% 根据边界条件调整矩阵
switch app.BCDropDown.Value
case '周期性边界'
A(1, app.N) = 1;
A(app.N, 1) = 1;
case '狄利克雷边界'
A(1, :) = 0;
A(app.N, :) = 0;
end
% 显式时间推进(前向Euler)
u_current = u0;
% 确定要存储结果的数组
if app.CompareWithFDMCheckBox.Value && strcmp(app.SpectralMethodDropDown.Value, '傅里叶谱方法')
% 如果作为比较方法,存储在u_fdm中
result_array = app.u_fdm;
else
% 否则存储在u_spectral中
result_array = app.u_spectral;
end
% 时间推进循环
for n = 1:length(app.t)-1
u_next = u_current + r * (A * u_current);
% 应用边界条件
switch app.BCDropDown.Value
case '狄利克雷边界'
u_next(1) = 0;
u_next(app.N) = 0;
end
if app.CompareWithFDMCheckBox.Value && strcmp(app.SpectralMethodDropDown.Value, '傅里叶谱方法')
app.u_fdm(:,n+1) = u_next;
else
app.u_spectral(:,n+1) = u_next;
end
u_current = u_next;
end
% 记录求解时间
if app.CompareWithFDMCheckBox.Value && strcmp(app.SpectralMethodDropDown.Value, '傅里叶谱方法')
% 如果作为比较方法,记录到fdmSolveTime
app.fdmSolveTime = toc;
else
% 否则记录到主求解时间
app.solveTime = toc;
% 设置状态标志
app.isSolved = true;
end
% 如果使用FDM作为主方法,且不需要比较,则不需要更新界面
% 因为调用者会负责更新
if ~(app.CompareWithFDMCheckBox.Value && strcmp(app.SpectralMethodDropDown.Value, '傅里叶谱方法'))
% 更新界面
app.updateVisualization();
app.updateErrorAnalysis();
end
end
% 使用傅里叶谱方法作为比较方法
function solve_fourier_for_comparison(app)
tic; % 开始计时
% 获取计算参数(共用主方法的参数)
% 空间和时间网格已经在主方法中设置
% 初始条件
u0 = app.getInitialCondition(app.x);
% 初始化解
app.u_spectral = zeros(app.N, length(app.t));
app.u_spectral(:,1) = u0;
% 计算波数
if mod(app.N, 2) == 0
k = 2*pi/app.L * [0:app.N/2-1, 0, -app.N/2+1:-1]';
else
k = 2*pi/app.L * [0:(app.N-1)/2, -(app.N-1)/2:-1]';
end
% 傅里叶变换初始条件
u_hat = fft(u0);
% 时间推进
for n = 1:length(app.t)-1
% 热方程的谱解:du_hat/dt = -alpha*k^2*u_hat
% 使用精确积分因子求解
u_hat = u_hat .* exp(-app.alpha*k.^2*app.dt);
% 反变换回物理空间
app.u_spectral(:,n+1) = real(ifft(u_hat));
end
% 记录求解时间
app.solveTime = toc;
end
% 更新可视化函数
function updateVisualization(app)
if ~app.isSolved
return;
end
% 获取时间滑块的当前值
timeIndex = round(app.TimeSlider.Value);
% 获取当前选择的方法
currentMethod = app.SpectralMethodDropDown.Value;
% 绘制解
cla(app.SolutionPlot);
hold(app.SolutionPlot, 'on');
% 绘制主方法解
plot(app.SolutionPlot, app.x, app.u_spectral(:,timeIndex), 'b-', 'LineWidth', 2);
% 如果有比较方法,也绘制
if app.CompareWithFDMCheckBox.Value
plot(app.SolutionPlot, app.x, app.u_fdm(:,timeIndex), 'r--', 'LineWidth', 1.5);
if strcmp(currentMethod, '傅里叶谱方法')
legend(app.SolutionPlot, '傅里叶谱方法', '有限差分法');
else
legend(app.SolutionPlot, '有限差分法', '傅里叶谱方法');
end
else
legend(app.SolutionPlot, currentMethod);
end
% 设置标题和轴标签
title(app.SolutionPlot, sprintf('t = %.4f', app.t(timeIndex)));
xlabel(app.SolutionPlot, 'x');
ylabel(app.SolutionPlot, 'u(x,t)');
grid(app.SolutionPlot, 'on');
% 更新当前时间标签
app.CurrentTimeLabel.Text = sprintf('当前时间: %.4f / %.4f', app.t(timeIndex), app.Tfinal);
hold(app.SolutionPlot, 'off');
end
% 更新误差分析函数
function updateErrorAnalysis(app)
if ~app.isSolved || ~app.CompareWithFDMCheckBox.Value
return;
end
% 获取当前选择的方法
currentMethod = app.SpectralMethodDropDown.Value;
% 计算两种方法的L2误差
error = zeros(size(app.t));
for i = 1:length(app.t)
error(i) = sqrt(mean((app.u_spectral(:,i) - app.u_fdm(:,i)).^2));
end
% 绘制误差随时间的变化
cla(app.ErrorPlot);
semilogy(app.ErrorPlot, app.t, error, 'k-', 'LineWidth', 1.5);
% 设置标题和轴标签
if strcmp(currentMethod, '傅里叶谱方法')
title(app.ErrorPlot, '傅里叶谱方法与有限差分法的L2误差');
else
title(app.ErrorPlot, '有限差分法与傅里叶谱方法的L2误差');
end
xlabel(app.ErrorPlot, '时间 t');
ylabel(app.ErrorPlot, 'L2误差');
grid(app.ErrorPlot, 'on');
% 计算速度对比
if strcmp(currentMethod, '傅里叶谱方法')
method1 = '傅里叶谱方法';
method2 = '有限差分法';
time1 = app.solveTime;
time2 = app.fdmSolveTime;
else
method1 = '有限差分法';
method2 = '傅里叶谱方法';
time1 = app.solveTime;
time2 = app.fdmSolveTime;
end
% 更新性能指标文本
app.PerformanceMetricsTextArea.Value = {
sprintf('%s计算时间: %.4f秒', method1, time1),
sprintf('%s计算时间: %.4f秒', method2, time2),
sprintf('加速比: %.2f倍', time2/time1),
sprintf('最大误差: %.6e', max(error)),
sprintf('平均误差: %.6e', mean(error)),
sprintf('空间点数: %d', app.N),
sprintf('时间步数: %d', length(app.t)),
sprintf('CFL数: %.4f', app.alpha*app.dt/(app.L/app.N)^2)
};
% 绘制性能对比条形图
cla(app.PerfBarAxes);
methods = {method1, method2};
times = [time1, time2];
bar(app.PerfBarAxes, times);
app.PerfBarAxes.XTickLabel = methods;
ylabel(app.PerfBarAxes, '计算时间 (秒)');
title(app.PerfBarAxes, '计算时间对比');
% 添加数值标签
for i = 1:length(times)
text(app.PerfBarAxes, i, times(i)*1.05, sprintf('%.4f秒', times(i)), ...
'HorizontalAlignment', 'center', 'FontSize', 9);
end
% 添加加速比标签
speedup = time2/time1;
text(app.PerfBarAxes, 1.5, min(times)*0.5, sprintf('加速比: %.2f倍', speedup), ...
'HorizontalAlignment', 'center', 'FontWeight', 'bold', 'FontSize', 11);
end
end
% 回调函数和界面事件处理
methods (Access = private)
% 点击求解按钮的回调函数
function SolveButtonPushed(app, ~)
% 更新状态
app.StatusLabel.Text = '计算中...';
try
app.StatusLabel.FontColor = [0.8 0.4 0];
catch
% 忽略错误,保持默认颜色
end
drawnow;
% 根据选择的方法进行求解
switch app.SpectralMethodDropDown.Value
case '傅里叶谱方法'
app.solve_fourier_spectral();
case '有限差分法'
app.solve_fdm();
end
% 设置时间滑块的范围
app.TimeSlider.Limits = [1, length(app.t)];
app.TimeSlider.Value = 1;
% 更新状态
app.StatusLabel.Text = '计算完成!';
try
app.StatusLabel.FontColor = [0 0.6 0];
catch
% 忽略错误,保持默认颜色
end
% 切换到可视化选项卡
app.TabGroup.SelectedTab = app.VisualizationTab;
end
% 时间滑块值改变的回调函数
function TimeSliderValueChanged(app, ~)
app.updateVisualization();
end
% 初始条件下拉列表值改变的回调函数
function ICDropDownValueChanged(app, ~)
app.updateICPreview();
end
% L值改变时的回调函数
function LEditFieldValueChanged(app, ~)
app.updateICPreview();
end
% 选择方法下拉列表值改变的回调函数
function SpectralMethodDropDownValueChanged(app, ~)
% 如果选择有限差分法,默认使用狄利克雷边界
if strcmp(app.SpectralMethodDropDown.Value, '有限差分法')
app.BCDropDown.Value = '狄利克雷边界';
else
app.BCDropDown.Value = '周期性边界';
end
end
% 播放/暂停按钮的回调函数
function PlayButtonPushed(app, ~)
% 如果正在播放,则暂停
if strcmp(app.PlayButton.Text, '暂停')
app.PlayButton.Text = '播放';
try
app.PlayButton.BackgroundColor = [0.3 0.8 0.3];
catch
% 忽略错误,保持默认颜色
end
stop(app.AnimationTimer);
return;
end
% 否则开始播放
app.PlayButton.Text = '暂停';
try
app.PlayButton.BackgroundColor = [0.8 0.3 0.3];
catch
% 忽略错误,保持默认颜色
end
% 获取动画速度
speed = 0.1; % 默认中速
switch app.AnimationSpeedDropDown.Value
case '慢速'
speed = 0.2;
case '中速'
speed = 0.1;
case '快速'
speed = 0.05;
end
% 创建并启动定时器
app.AnimationTimer = timer('ExecutionMode', 'fixedRate', ...
'Period', speed, ...
'TimerFcn', @(~,~) app.animationStep());
start(app.AnimationTimer);
end
% 动画步进函数
function animationStep(app)
% 获取当前滑块值
currentIndex = app.TimeSlider.Value;
% 如果已经到达最后一个时间点,则重置到开始
if currentIndex >= app.TimeSlider.Limits(2)
currentIndex = app.TimeSlider.Limits(1);
else
% 否则前进到下一个时间点
currentIndex = currentIndex + 1;
end
% 更新滑块值
app.TimeSlider.Value = currentIndex;
% 更新可视化
app.updateVisualization();
end
end
% 组件初始化和创建函数
methods (Access = private)
% 创建UI组件
function createComponents(app)
% 检查MATLAB版本,R2019b及以上支持uiaxes
hasUIAxes = ~verLessThan('matlab', '9.7'); % R2019b版本号是9.7
% 创建主窗口
app.UIFigure = uifigure('Visible', 'off');
app.UIFigure.Position = [100, 100, 900, 700];
app.UIFigure.Name = '一维热传导方程谱方法求解器';
app.UIFigure.Color = [0.94 0.94 0.94];
% 创建选项卡组
app.TabGroup = uitabgroup(app.UIFigure);
app.TabGroup.Position = [1, 1, 900, 700];
app.TabGroup.TabLocation = 'left';
% 创建设置选项卡
app.SettingsTab = uitab(app.TabGroup);
app.SettingsTab.Title = '参数设置';
app.SettingsTab.BackgroundColor = [0.94 0.94 0.94];
% 创建面板来组织控件
paramPanel = uipanel(app.SettingsTab);
paramPanel.Title = '计算参数';
paramPanel.FontWeight = 'bold';
paramPanel.FontSize = 14;
paramPanel.Position = [30, 400, 400, 300];
paramPanel.BackgroundColor = [0.97 0.97 0.97];
bcPanel = uipanel(app.SettingsTab);
bcPanel.Title = '条件设置';
bcPanel.FontWeight = 'bold';
bcPanel.FontSize = 14;
bcPanel.Position = [460, 400, 400, 250];
bcPanel.BackgroundColor = [0.97 0.97 0.97];
methodPanel = uipanel(app.SettingsTab);
methodPanel.Title = '数值方法设置';
methodPanel.FontWeight = 'bold';
methodPanel.FontSize = 14;
methodPanel.Position = [30, 150, 830, 220];
methodPanel.BackgroundColor = [0.97 0.97 0.97];
% 创建空间区域设置组件
app.DomainLabel = uilabel(paramPanel);
app.DomainLabel.Position = [20, 260, 200, 22];
app.DomainLabel.Text = '计算域设置';
app.DomainLabel.FontWeight = 'bold';
app.DomainLabel.FontSize = 12;
app.LLabel = uilabel(paramPanel);
app.LLabel.Position = [20, 240, 150, 22];
app.LLabel.Text = '域长度 L:';
app.LEditField = uieditfield(paramPanel, 'numeric');
app.LEditField.Position = [180, 240, 100, 22];
app.LEditField.Value = 1;
app.LEditField.Limits = [0.1, 10];
app.LEditField.ValueDisplayFormat = '%.3f';
app.NLabel = uilabel(paramPanel);
app.NLabel.Position = [20, 210, 150, 22];
app.NLabel.Text = '空间点数 N:';
app.NEditField = uieditfield(paramPanel, 'numeric');
app.NEditField.Position = [180, 210, 100, 22];
app.NEditField.Value = 128;
app.NEditField.Limits = [16, 1024];
% 创建时间设置组件
app.TimeLabel = uilabel(paramPanel);
app.TimeLabel.Position = [20, 180, 200, 22];
app.TimeLabel.Text = '时间设置';
app.TimeLabel.FontWeight = 'bold';
app.TimeLabel.FontSize = 12;
app.TfinalLabel = uilabel(paramPanel);
app.TfinalLabel.Position = [20, 150, 150, 22];
app.TfinalLabel.Text = '终止时间:';
app.TfinalEditField = uieditfield(paramPanel, 'numeric');
app.TfinalEditField.Position = [180, 150, 100, 22];
app.TfinalEditField.Value = 0.1;
app.TfinalEditField.Limits = [0.001, 10];
app.TfinalEditField.ValueDisplayFormat = '%.4f';
app.dtLabel = uilabel(paramPanel);
app.dtLabel.Position = [20, 120, 150, 22];
app.dtLabel.Text = '时间步长:';
app.dtEditField = uieditfield(paramPanel, 'numeric');
app.dtEditField.Position = [180, 120, 100, 22];
app.dtEditField.Value = 0.001;
app.dtEditField.Limits = [1e-6, 1];
app.dtEditField.ValueDisplayFormat = '%.6f';
% 创建物理参数设置组件
app.PhysicalParamsLabel = uilabel(paramPanel);
app.PhysicalParamsLabel.Position = [20, 90, 200, 22];
app.PhysicalParamsLabel.Text = '物理参数';
app.PhysicalParamsLabel.FontWeight = 'bold';
app.PhysicalParamsLabel.FontSize = 12;
app.AlphaLabel = uilabel(paramPanel);
app.AlphaLabel.Position = [20, 60, 150, 22];
app.AlphaLabel.Text = '热扩散系数 α:';
app.AlphaEditField = uieditfield(paramPanel, 'numeric');
app.AlphaEditField.Position = [180, 60, 100, 22];
app.AlphaEditField.Value = 0.01;
app.AlphaEditField.Limits = [0.0001, 1];
app.AlphaEditField.ValueDisplayFormat = '%.5f';
% 创建初始条件和边界条件设置组件
app.ICLabel = uilabel(bcPanel);
app.ICLabel.Position = [20, 180, 150, 22];
app.ICLabel.Text = '初始条件:';
app.ICLabel.FontWeight = 'bold';
app.ICDropDown = uidropdown(bcPanel);
app.ICDropDown.Items = {'高斯脉冲', '阶跃函数', '正弦函数', '自定义'};
app.ICDropDown.Value = '高斯脉冲';
app.ICDropDown.Position = [180, 180, 180, 22];
app.ICDropDown.BackgroundColor = [1 1 1];
% 添加初始条件预览图
app.ICPreviewPanel = uipanel(bcPanel);
app.ICPreviewPanel.Title = '初始条件预览';
app.ICPreviewPanel.Position = [20, 70, 340, 100];
% 根据MATLAB版本选择绘图函数
if hasUIAxes
% 使用新版的uiaxes
app.ICPreviewAxes = uiaxes(app.ICPreviewPanel);
else
% 使用传统的axes
app.ICPreviewAxes = axes('Parent', app.ICPreviewPanel);
end
app.ICPreviewAxes.Position = [10, 10, 320, 70];
app.ICPreviewAxes.XGrid = 'on';
app.ICPreviewAxes.YGrid = 'on';
app.ICPreviewAxes.Box = 'on';
app.BCLabel = uilabel(bcPanel);
app.BCLabel.Position = [20, 40, 150, 22];
app.BCLabel.Text = '边界条件:';
app.BCLabel.FontWeight = 'bold';
app.BCDropDown = uidropdown(bcPanel);
app.BCDropDown.Items = {'周期性边界', '狄利克雷边界'};
app.BCDropDown.Value = '周期性边界';
app.BCDropDown.Position = [180, 40, 180, 22];
app.BCDropDown.BackgroundColor = [1 1 1];
% 在方法面板中创建数值方法设置组件
app.MethodLabel = uilabel(methodPanel);
app.MethodLabel.Position = [20, 170, 150, 22];
app.MethodLabel.Text = '求解方法:';
app.MethodLabel.FontWeight = 'bold';
app.SpectralMethodDropDown = uidropdown(methodPanel);
app.SpectralMethodDropDown.Items = {'傅里叶谱方法', '有限差分法'};
app.SpectralMethodDropDown.Value = '傅里叶谱方法';
app.SpectralMethodDropDown.Position = [180, 170, 180, 22];
app.SpectralMethodDropDown.BackgroundColor = [1 1 1];
app.CompareWithFDMCheckBox = uicheckbox(methodPanel);
app.CompareWithFDMCheckBox.Text = '与另一种方法比较';
app.CompareWithFDMCheckBox.Position = [400, 170, 200, 22];
app.CompareWithFDMCheckBox.Value = true;
app.CompareWithFDMCheckBox.FontWeight = 'bold';
% 添加方法说明文本
methodInfoText = uitextarea(methodPanel);
methodInfoText.Position = [20, 50, 790, 100];
methodInfoText.Value = {
'傅里叶谱方法: 适用于周期性边界条件,使用FFT进行加速计算,对光滑解具有指数收敛特性。',
'有限差分法: 经典的数值方法,使用网格点上的差分近似导数,精度取决于网格密度。',
'',
'对于周期性边界条件,傅里叶谱方法通常更高效、更精确。',
'对于狄利克雷边界条件,有限差分法实现简单,适用性广泛。'
};
methodInfoText.FontSize = 11;
methodInfoText.Editable = 'off';
% 创建求解按钮 - 使其更突出
app.SolveButton = uibutton(app.SettingsTab, 'push');
app.SolveButton.ButtonPushedFcn = createCallbackFcn(app, @SolveButtonPushed, true);
app.SolveButton.Position = [375, 60, 150, 50];
app.SolveButton.Text = '开始求解';
app.SolveButton.FontSize = 16;
app.SolveButton.FontWeight = 'bold';
try
% 尝试设置按钮颜色,如果不支持则忽略
app.SolveButton.BackgroundColor = [0.3 0.6 0.9];
app.SolveButton.FontColor = [1 1 1];
catch
% 忽略错误,保持默认颜色
end
% 创建状态标签
app.StatusLabel = uilabel(app.SettingsTab);
app.StatusLabel.Position = [350, 30, 200, 22];
app.StatusLabel.Text = '准备就绪';
app.StatusLabel.HorizontalAlignment = 'center';
% 创建可视化选项卡
app.VisualizationTab = uitab(app.TabGroup);
app.VisualizationTab.Title = '可视化';
app.VisualizationTab.BackgroundColor = [0.94 0.94 0.94];
% 创建可视化控制面板
visControlPanel = uipanel(app.VisualizationTab);
visControlPanel.Title = '可视化控制';
visControlPanel.FontWeight = 'bold';
visControlPanel.FontSize = 14;
visControlPanel.Position = [30, 30, 830, 100];
visControlPanel.BackgroundColor = [0.97 0.97 0.97];
% 创建解的图形面板
visSolutionPanel = uipanel(app.VisualizationTab);
visSolutionPanel.Title = '温度场分布';
visSolutionPanel.FontWeight = 'bold';
visSolutionPanel.FontSize = 14;
visSolutionPanel.Position = [30, 150, 830, 500];
visSolutionPanel.BackgroundColor = [0.97 0.97 0.97];
% 创建解的图形
if hasUIAxes
% 使用新版的uiaxes
app.SolutionPlot = uiaxes(visSolutionPanel);
else
% 使用传统的axes
app.SolutionPlot = axes('Parent', visSolutionPanel);
end
app.SolutionPlot.Position = [20, 20, 790, 460];
title(app.SolutionPlot, '温度场分布');
xlabel(app.SolutionPlot, '空间坐标 x');
ylabel(app.SolutionPlot, '温度 u(x,t)');
grid(app.SolutionPlot, 'on');
app.SolutionPlot.Box = 'on';
app.SolutionPlot.FontSize = 12;
% 创建时间滑块
app.TimeSlider = uislider(visControlPanel);
app.TimeSlider.Position = [150, 50, 500, 3];
app.TimeSlider.Limits = [1, 100];
app.TimeSlider.Value = 1;
app.TimeSlider.ValueChangedFcn = createCallbackFcn(app, @TimeSliderValueChanged, true);
% 添加播放/暂停按钮
app.PlayButton = uibutton(visControlPanel, 'push');
app.PlayButton.Position = [50, 45, 80, 30];
app.PlayButton.Text = '播放';
try
% 尝试设置按钮颜色,如果不支持则忽略
app.PlayButton.BackgroundColor = [0.3 0.8 0.3];
app.PlayButton.FontColor = [1 1 1];
catch
% 忽略错误,保持默认颜色
end
app.PlayButton.ButtonPushedFcn = createCallbackFcn(app, @PlayButtonPushed, true);
% 添加时间步长控制
app.AnimationSpeedLabel = uilabel(visControlPanel);
app.AnimationSpeedLabel.Position = [670, 50, 80, 22];
app.AnimationSpeedLabel.Text = '动画速度:';
app.AnimationSpeedDropDown = uidropdown(visControlPanel);
app.AnimationSpeedDropDown.Items = {'慢速', '中速', '快速'};
app.AnimationSpeedDropDown.Value = '中速';
app.AnimationSpeedDropDown.Position = [750, 50, 60, 22];
app.CurrentTimeLabel = uilabel(visControlPanel);
app.CurrentTimeLabel.Position = [330, 15, 200, 22];
app.CurrentTimeLabel.Text = '当前时间: 0.0000 / 0.1000';
app.CurrentTimeLabel.HorizontalAlignment = 'center';
% 创建误差分析选项卡
app.ErrorAnalysisTab = uitab(app.TabGroup);
app.ErrorAnalysisTab.Title = '误差分析';
app.ErrorAnalysisTab.BackgroundColor = [0.94 0.94 0.94];
% 创建误差图形面板
errorPanel = uipanel(app.ErrorAnalysisTab);
errorPanel.Title = '误差分析';
errorPanel.FontWeight = 'bold';
errorPanel.FontSize = 14;
errorPanel.Position = [30, 350, 830, 300];
errorPanel.BackgroundColor = [0.97 0.97 0.97];
% 创建性能指标面板
perfPanel = uipanel(app.ErrorAnalysisTab);
perfPanel.Title = '性能指标';
perfPanel.FontWeight = 'bold';
perfPanel.FontSize = 14;
perfPanel.Position = [30, 30, 830, 300];
perfPanel.BackgroundColor = [0.97 0.97 0.97];
% 创建误差图形
if hasUIAxes
% 使用新版的uiaxes
app.ErrorPlot = uiaxes(errorPanel);
else
% 使用传统的axes
app.ErrorPlot = axes('Parent', errorPanel);
end
app.ErrorPlot.Position = [20, 20, 790, 260];
title(app.ErrorPlot, '谱方法与有限差分法的误差对比');
xlabel(app.ErrorPlot, '时间 t');
ylabel(app.ErrorPlot, 'L2误差');
grid(app.ErrorPlot, 'on');
app.ErrorPlot.Box = 'on';
app.ErrorPlot.YScale = 'log';
app.ErrorPlot.FontSize = 12;
% 创建性能指标文本区域
app.PerformanceMetricsTextArea = uitextarea(perfPanel);
app.PerformanceMetricsTextArea.Position = [20, 100, 400, 180];
app.PerformanceMetricsTextArea.Value = {'性能指标将在求解后显示'};
app.PerformanceMetricsTextArea.FontSize = 12;
app.PerformanceMetricsTextArea.Editable = 'off';
% 创建性能指标可视化
if hasUIAxes
% 使用新版的uiaxes
app.PerfBarAxes = uiaxes(perfPanel);
else
% 使用传统的axes
app.PerfBarAxes = axes('Parent', perfPanel);
end
app.PerfBarAxes.Position = [450, 100, 360, 180];
title(app.PerfBarAxes, '计算时间对比');
app.PerfBarAxes.XTickLabelRotation = 45;
app.PerfBarAxes.Box = 'on';
app.PerfBarAxes.FontSize = 11;
% 创建帮助选项卡
app.HelpTab = uitab(app.TabGroup);
app.HelpTab.Title = '帮助';
app.HelpTab.BackgroundColor = [0.94 0.94 0.94];
% 添加帮助信息
helpTextArea = uitextarea(app.HelpTab);
helpTextArea.Position = [30, 30, 830, 620];
helpTextArea.Value = {
'一维热传导方程谱方法求解器使用说明',
'==========================================',
'',
'1. 参数设置选项卡:',
' - 计算域参数: 设置空间域长度和离散点数',
' - 时间参数: 设置仿真终止时间和时间步长',
' - 物理参数: 设置热扩散系数',
' - 初始条件: 选择初始温度分布',
' - 边界条件: 选择边界类型',
' - 求解方法: 选择傅里叶谱方法或有限差分法',
'',
'2. 可视化选项卡:',
' - 使用时间滑块观察不同时刻的温度分布',
' - 使用播放按钮自动播放温度场演化过程',
' - 调整动画速度控制播放速率',
'',
'3. 误差分析选项卡:',
' - 查看谱方法与有限差分法的误差对比',
' - 查看性能指标,包括计算时间和加速比',
'',
'算法说明:',
'=========',
'热传导方程: ∂u/∂t = α ∂²u/∂x²',
'',
'傅里叶谱方法原理:',
'1. 将解展开为傅里叶级数',
'2. 在频域中计算空间导数',
'3. 使用FFT进行物理空间和频域之间的变换',
'4. 采用时间积分方法求解',
'',
'有限差分法: 经典的数值方法,使用网格点上的差分近似导数,精度取决于网格密度。',
'',
'更多信息请参考文档。'
};
helpTextArea.FontSize = 12;
helpTextArea.Editable = 'off';
% 选择第一个选项卡作为默认选项卡
app.TabGroup.SelectedTab = app.SettingsTab;
end
end
% 公共方法
methods (Access = public)
% 构造函数
function app = HeatEquationSolver
% 创建UI组件
createComponents(app)
% 初始化属性
app.isSolved = false;
% 设置回调函数
app.ICDropDown.ValueChangedFcn = createCallbackFcn(app, @ICDropDownValueChanged, true);
app.LEditField.ValueChangedFcn = createCallbackFcn(app, @LEditFieldValueChanged, true);
app.SpectralMethodDropDown.ValueChangedFcn = createCallbackFcn(app, @SpectralMethodDropDownValueChanged, true);
% 初始化初始条件预览
app.updateICPreview();
% 显示UI
app.UIFigure.Visible = 'on';
end
% 析构函数 - 清理定时器
function delete(app)
% 停止并删除动画定时器(如果存在)
if ~isempty(app.AnimationTimer) && isvalid(app.AnimationTimer)
stop(app.AnimationTimer);
delete(app.AnimationTimer);
end
% 删除组件
delete(app.UIFigure);
end
end
end代码实现后,选用傅里叶解为主方法同时勾选与另一种方法对比时,主方法的可视化动画无法实现怎么修改