Simulink PI 参数扫描与批量仿真完整 MATLAB 代码
手动逐次修改参数容易混用模型和结果。本文把 PI 闭环的 12 组参数交给同一个函数串行仿真,统一计算指标、记录失败原因,并导出曲线和排序表。
1 方法原理
被控对象为 G(s)=1/(s+1),控制器为 C(s)=Kp+Ki/s,采用单位负反馈与零初值。输入为 t=0 的单位阶跃。扫描 Kp=0.5,1,2,4、Ki=0.5,1,2,共 4×3=12 组。
每次仿真使用 Simulink.SimulationInput 覆盖模型工作区中的 Kp、Ki,避免循环间参数串用。求解器为 ode4,固定步长 0.01 s,仿真时间 0 至 10 s。程序串行运行,不启动并行池。
性能指标为 IAE=∫|1−y(t)|dt、单位阶跃超调百分比、2% 带内调节时间和终点绝对误差。调节时间要求末尾连续在带内至少 0.5 s;条件不足则保留 NaN 和 false,不补造一个调节时间。
每轮用 try/catch 单独记录错误,并写出当前汇总表。排序只考虑成功工况,以本次网格和时间窗口的 IAE 为依据;它不代表全局最优,也没有纳入控制量、执行器饱和或鲁棒性约束。
2 MATLAB 程序
需要 MATLAB 与 Simulink,不使用 Control System Toolbox 或 Parallel Computing Toolbox。将完整代码保存为 article2_batch_sweep.m,确保文件名与首行函数名一致,并在该文件所在目录执行下列调用。所有本地函数均需保留。
运行命令
R, modelFile, outDir = article2_batch_sweep;
open_system(modelFile)
完整程序
function results, modelFile, outDir = article2_batch_sweep(outRoot)
% Build a PI loop, run a serial 4-by-3 sweep, export actual results.
% Requires MATLAB and Simulink. No Control System Toolbox or PCT used.
% Reviewed against MathWorks documentation; not executed in MATLAB here.
% Example: R, modelFile, outDir = article2_batch_sweep;
% open_system(modelFile)
% 可选参数指定输出根目录;不提供时写到当前目录下的 article2_runs。
if nargin < 1
outRoot = fullfile(pwd, 'article2_runs');
end
if ~exist(outRoot, 'dir'), mkdir(outRoot); end
outDir = tempname(outRoot); % New folder; preserve previous runs.
mkdir(outDir);
\~, uid = fileparts(outDir);
mdl = 'csdn_pi_' regexprep(uid, '\[\^a-zA-Z0-9_', '_')];
mdl = mdl(1:min(numel(mdl), namelengthmax));
assert(~bdIsLoaded(mdl), 'Model name already loaded: %s', mdl);
% 统一各工况的时间窗口、积分步长和最短带内持续时间。
T = 10; h = 0.01; minHold = 0.5;
load_system('simulink');
new_system(mdl);
cleanupModel = onCleanup(@() closeIfLoaded(mdl)); %#ok<NASGU>
buildLoop(mdl, T, h);
modelFile = fullfile(outDir, mdl '.slx');
save_system(mdl, modelFile);
% 生成笛卡尔积网格,随后按列展开为 12 组参数。
KpGrid, KiGrid = ndgrid(0.5 1 2 4, 0.5 1 2);
kp = KpGrid(:); ki = KiGrid(:); n = numel(kp);
% 预分配汇总表,尚未完成的工况标记为 pending。
results = table((1:n)', kp, ki, nan(n,1), nan(n,1), ...
nan(n,1), false(n,1), nan(n,1), ...
repmat({'pending'},n,1), repmat({''},n,1), ...
'VariableNames', {'Run','Kp','Ki','IAE','Overshoot_pct', ...
'Settling2pct_s','SettledInWindow','EndAbsError', ...
'Status','Message'});
traces = cell(n,1);
% 逐组串行仿真,单组出错时记录原因并继续下一组。
for k = 1:n
try
% 通过 SimulationInput 指定本次仿真输入,不直接改基础工作区。
in = Simulink.SimulationInput(mdl);
in = setVariable(in, 'Kp', kp(k), 'Workspace', mdl);
in = setVariable(in, 'Ki', ki(k), 'Workspace', mdl);
in = setModelParameter(in, 'StopTime', num2str(T));
out = sim(in); % Deliberately serial, no parallel pool.
ts = get(out, 'y');
t = double(ts.Time(:)); y = double(ts.Data(:));
assert(numel(t) == numel(y) && numel(t) >= 2, ...
'Expected scalar output and at least two samples.');
assert(all(isfinite(t)) && all(isfinite(y)) ...
&& all(diff(t) > 0), 'Invalid or nonfinite log.');
assert(abs(t(1)) < 1e-9 && abs(t(end)-T) < 1e-8, ...
'Simulation did not cover the full time window.');
% 单位阶跃参考恒为 1,误差由参考减去输出得到。
e = 1-y; % Unit reference starts at time zero.
results.IAE(k) = trapz(t, abs(e));
results.Overshoot_pct(k) = 100*max(0, max(y)-1);
% 调节时间仅在有限观察窗口内判定;末段须至少保持 0.5 s。
lastOutside = find(abs(e) > 0.02, 1, 'last');
if isempty(lastOutside)
candidate = t(1);
elseif lastOutside < numel(t)
candidate = t(lastOutside+1);
else
candidate = NaN;
end
if isfinite(candidate) && T-candidate >= minHold-1e-9
results.Settling2pct_s(k) = candidate;
results.SettledInWindow(k) = true;
end
results.EndAbsError(k) = abs(e(end));
traces{k} = table(t, y, e, ...
'VariableNames', {'Time_s','Output','Error'});
writetable(traces{k}, fullfile(outDir, ...
sprintf('trace_%02d.csv',k)));
results.Status{k} = 'ok';
% 失败工况的指标恢复为 NaN,状态和原始报错写入汇总。
catch ME
results.IAE(k) = NaN;
results.Overshoot_pct(k) = NaN;
results.Settling2pct_s(k) = NaN;
results.SettledInWindow(k) = false;
results.EndAbsError(k) = NaN;
traces{k} = \[\];
results.Status{k} = 'error';
results.Message{k} = ME.message;
end
% Keep completed rows even if a later run is interrupted.
writetable(results, fullfile(outDir, 'sweep_summary.csv'));
end
% 筛选成功工况后按 IAE 排序,不让失败行参与候选比较。
ok = strcmp(results.Status, 'ok');
ranked = sortrows(results(ok,:), 'IAE');
writetable(ranked, fullfile(outDir, 'ranking_by_IAE.csv'));
save(fullfile(outDir, 'sweep_data.mat'), ...
'results', 'traces', 'T', 'h', 'minHold', 'modelFile');
if any(ok)
makePlots(results, traces, ok, T, outDir);
disp('Lowest IAE within this tested grid and time window:');
disp(ranked(1,:));
else
warning('Sweep:NoSuccessfulRuns', ...
'All runs failed. Inspect sweep_summary.csv.');
end
fprintf('Model: %s\nOutputs: %s\n', modelFile, outDir);
end
% 本地函数:创建 PI 负反馈模型,参数保存在模型工作区。
function buildLoop(mdl, T, h)
set_param(mdl, 'StartTime','0', 'StopTime',num2str(T), ...
'SolverType','Fixed-step', 'Solver','ode4', ...
'FixedStep',num2str(h), 'ReturnWorkspaceOutputs','on');
mw = get_param(mdl, 'ModelWorkspace');
mw.DataSource = 'Model File';
assignin(mw, 'Kp', 1); assignin(mw, 'Ki', 1);
add_block('simulink/Sources/Step', mdl '/Step', ...
'Time','0', 'Before','0', 'After','1', ...
'SampleTime','0', 'Position',30 110 60 140);
add_block('simulink/Math Operations/Sum', mdl '/Error', ...
'Inputs','+-', 'Position',110 107 135 143);
add_block('simulink/Math Operations/Gain', mdl '/P', ...
'Gain','Kp', 'Position',195 65 245 105);
add_block('simulink/Math Operations/Gain', mdl '/I_Gain', ...
'Gain','Ki', 'Position',195 165 245 205);
add_block('simulink/Continuous/Integrator', mdl '/Integrator', ...
'InitialCondition','0', 'Position',285 165 320 205);
add_block('simulink/Math Operations/Sum', mdl '/PI_Sum', ...
'Inputs','++', 'Position',365 107 390 143);
add_block('simulink/Continuous/Transfer Fcn', mdl '/Plant', ...
'Numerator','1', 'Denominator','1 1', ...
'Position',440 107 520 143);
add_block('simulink/Sinks/To Workspace', mdl '/Y', ...
'VariableName','y', 'SaveFormat','Timeseries', ...
'MaxDataPoints','inf', 'Decimation','1', ...
'SampleTime','-1', 'Position',590 107 680 143);
% 连接顺序确保输出回到误差求和的负端口。
connections = {'Step/1','Error/1'; 'Error/1','P/1'; ...
'Error/1','I_Gain/1'; 'I_Gain/1','Integrator/1'; ...
'P/1','PI_Sum/1'; 'Integrator/1','PI_Sum/2'; ...
'PI_Sum/1','Plant/1'; 'Plant/1','Y/1'; ...
'Plant/1','Error/2'};
for j = 1:size(connections,1)
add_line(mdl, connections{j,1}, connections{j,2}, ...
'autorouting','on');
end
end
% 本地函数:只绘制成功工况,生成响应叠加图和网格指标图。
function makePlots(R, traces, ok, T, outDir)
f = figure('Visible','off', 'Color','w', ...
'Position',100 100 1000 600);
cleanupFigure = onCleanup(@() close(f)); %#ok<NASGU>
ax = axes('Parent',f); hold(ax,'on');
ids = find(ok);
for j = 1:numel(ids)
k = ids(j); D = traces{k};
plot(ax, D.Time_s, D.Output, 'LineWidth',1.1, ...
'DisplayName',sprintf('Kp=%.2g, Ki=%.2g',R.Kp(k),R.Ki(k)));
end
plot(ax, 0 T, 1 1, 'k--', 'DisplayName','Reference');
xlabel(ax,'Time (s)'); ylabel(ax,'Output'); grid(ax,'on');
title(ax,'PI parameter sweep: simulated responses');
legend(ax,'Location','eastoutside');
print(f, fullfile(outDir,'responses.png'), '-dpng','-r150');
clf(f); ax = axes('Parent',f);
scatter(ax, R.Kp(ok), R.Ki(ok), 170, R.IAE(ok), 'filled');
xlabel(ax,'Kp'); ylabel(ax,'Ki'); grid(ax,'on'); colorbar(ax);
title(ax,sprintf('Tested grid: color = IAE over 0 to %.3g s',T));
print(f, fullfile(outDir,'grid_IAE.png'), '-dpng','-r150');
end
% 清理函数:仅关闭本函数创建的模型,不保存临时仿真覆盖参数。
function closeIfLoaded(mdl)
if bdIsLoaded(mdl), close_system(mdl,0); end
end
3 程序结果
