Simulink PI 参数扫描与批量仿真完整 MATLAB 代码

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 程序结果

相关推荐
Want5952 小时前
Python跳动的爱心②
开发语言·python·数学建模
小羊没烦恼!3 小时前
T-SQL查询进阶--理解SQL SERVER中的分区表
服务器·开发语言·前端·javascript·html
爱吃香菜的初学者4 小时前
十六.Linux——多线程(2)
linux·运维·开发语言
jeo陽4 小时前
04-循环实战:素数、最大公约数、级数求和与枚举法
c语言·开发语言·数据结构
暖核4 小时前
kubeadm 集群运维:Worker 节点下线删除、整套集群销毁重建指南
运维·开发语言·php
tachibana25 小时前
Go 语言里的 Context 是什么?
开发语言·后端·golang·go·语言
天桥下的卖艺者5 小时前
R语言使用ggcube包轻松绘制文章中的各类3D图形
开发语言·3d·r语言
slandarer5 小时前
MATLAB | 趣味编程——动态字符串甜甜圈和爱心
数据结构·matlab·趣味编程
唐钰小宝B6 小时前
深度解读 JDK 8、JDK 11、JDK 17 和 JDK 21 的区别
java·开发语言·windows