實(shí)現(xiàn)與自動調(diào)參工程實(shí)踐)
簡介面向需要在Simulink環(huán)境中調(diào)試S函數(shù)參數(shù)的MATLAB學(xué)習(xí)者與工程人員這份資料圍繞M文件驅(qū)動的自動調(diào)參展開可用于減少手工反復(fù)修改參數(shù)的繁瑣流程適合具備一定Simulink建模與腳本基礎(chǔ)的中級用戶。壓縮包共4個文件由3個m腳本和1個slx模型組成整體約24KB腳本承擔(dān)參數(shù)掃描、仿真控制與結(jié)果篩選等任務(wù)模型文件提供可運(yùn)行的S函數(shù)仿真載體。已有3819人學(xué)習(xí)下載說明該主題在控制系統(tǒng)仿真與算法驗(yàn)證中具有一定關(guān)注度。讀者可據(jù)此理解自動調(diào)參的組織方式復(fù)用腳本框架完成批量仿真、參數(shù)對比與較優(yōu)參數(shù)選取并借助配套模型快速驗(yàn)證思路降低重復(fù)搭建與調(diào)試成本。1. 從一次通宵調(diào)參說起S 函數(shù)參數(shù)化能省掉什么某開發(fā)者接了個控制回路整定的活被控對象和控制器都封在 Simulink 模型里控制器本體是一個 S 函數(shù)真正要動的只有 Kp、Ki 兩個增益。他一開始靠手改對話框、點(diǎn)一次運(yùn)行、看一眼示波器改到第八版時已經(jīng)分不清哪條曲線對應(yīng)哪組數(shù)了。后來他把 S 函數(shù)換成 M 文件實(shí)現(xiàn)參數(shù)從模塊對話框讀再寫一個腳本循環(huán)喂參數(shù)、跑仿真、算誤差積分一晚上掃完兩百多組第二天拿最優(yōu)值收工。這套「MATLAB 里用 M 文件寫 Simulink 的 S 函數(shù)再掛一層自動調(diào)參腳本」的組合適合控制邏輯已經(jīng)沉淀在 S 函數(shù)里、又不想靠手感試參數(shù)的人也適合剛接觸 Level-2 M 文件 S 函數(shù)、想弄清它和外部腳本怎么配合的讀者。下面按工程骨架、回調(diào)實(shí)現(xiàn)、調(diào)參引擎、踩坑排查、二次開發(fā)幾段拆開講。2. 工程骨架M 文件 S 函數(shù)、調(diào)參腳本與結(jié)果目錄怎么擺2.1 目錄結(jié)構(gòu)與文件職責(zé)這類項(xiàng)目最怕的是所有東西堆在一個文件夾里模型、S 函數(shù)、臨時腳本、結(jié)果數(shù)據(jù)混在一起跑上十幾輪以后自己都找不到哪個 .mat 是哪次實(shí)驗(yàn)的。我一般按「模型 / 被調(diào)對象 / 調(diào)參邏輯 / 結(jié)果」四層拆目錄名盡量短方便addpath之后一眼看出哪個文件歸哪一層。sfunc_tune/ ├─ init_project.m % 統(tǒng)一入口加路徑、清緩存、設(shè)默認(rèn)參數(shù) ├─ models/ │ └─ loop_model.slx % 頂層模型里面放 S-Function 模塊 ├─ sfunc/ │ └─ msfcn_pid.m % Level-2 M 文件 S 函數(shù)本體 ├─ tune/ │ ├─ tune_main.m % 調(diào)參主流程網(wǎng)格 局部搜索 │ ├─ cost_once.m % 目標(biāo)函數(shù)一次仿真返回一個標(biāo)量代價 │ ├─ build_grid.m % 生成候選參數(shù)組合 │ └─ save_result.m % 結(jié)果落盤帶時間戳 └─ results/ ├─ run_20240101_0930.mat └─ run_20240101_0930.csvinit_project.m里干三件事把sfunc和tune加進(jìn)搜索路徑檢查loop_model.slx能不能load_system清掉上一次可能殘留的基礎(chǔ)工作區(qū)變量。第三件事看著多余實(shí)際能避免很多「明明改了參數(shù)結(jié)果沒變」的假象——變量名撞上舊值仿真照樣跑只是跑的是舊參數(shù)。function init_project() % 工程統(tǒng)一入口路徑、模型、殘留變量三件事 root fileparts(mfilename(fullpath)); addpath(fullfile(root, sfunc)); addpath(fullfile(root, tune)); mdl loop_model; if ~bdIsLoaded(mdl) load_system(fullfile(root, models, [mdl .slx])); end % 清掉可能被上一次實(shí)驗(yàn)寫臟的變量 vars {Kp, Ki, simOut, yout}; for k 1:numel(vars) if evalin(base, [exist( vars{k} , var)]) evalin(base, [clear vars{k}]); end end fprintf([init] 工程已就緒模型%s\n, mdl); endmfilename(fullpath)拿的是當(dāng)前文件所在目錄這樣整個工程搬到別的機(jī)器上不用改路徑。bdIsLoaded判斷模型是否已經(jīng)在內(nèi)存里避免重復(fù)load_system報(bào)警告。最后那段循環(huán)用evalin(base, ...)而不是直接clear是因?yàn)楹瘮?shù)里的工作區(qū)和基礎(chǔ)工作區(qū)是兩回事S 函數(shù)讀的是基礎(chǔ)工作區(qū)里的變量清錯地方等于沒清。2.2 一條參數(shù)從腳本到 S 函數(shù)的完整鏈路很多人卡在第一步腳本里算出來的參數(shù)怎么讓 Simulink 里的 S 函數(shù)讀到。常見做法有三條路選哪條取決于你要不要保留「手動改參數(shù)」的能力。第一條是直接把數(shù)值寫進(jìn) S-Function 模塊的對話框參數(shù)用set_param改。好處是自包含壞處是每改一次都要動模型反復(fù)set_param會讓模型變臟跑完還得決定要不要保存。第二條是模塊對話框里填變量名比如Kp、Ki腳本往基礎(chǔ)工作區(qū)里寫同名變量。這條路最靈活同一個模型既能手動跑也能腳本跑缺點(diǎn)是變量泄漏跑完不清理會污染下一輪。第三條是用Simulink.SimulationInput的setVariable把變量綁定到這一次仿真上作用域干凈還能配合parsim并行。我一般首選這條只有在需要兼容老版本模型時才退回第二條。完整鏈路是這樣的tune_main生成候選參數(shù) →cost_once用SimulationInput綁定Kp/Ki→sim跑一次 → 從輸出對象里取響應(yīng) → 算出代價 → 返回給搜索器 → 記錄一行結(jié)果。S 函數(shù)那一端只認(rèn)模塊對話框里的兩個變量名完全不知道外面在掃參。2.3 為什么用 Level-2 M 文件 S 函數(shù)而不是 C-MEX標(biāo)題里點(diǎn)的是 M 文件實(shí)現(xiàn)這里得說清楚取舍。Level-2 M 文件 S 函數(shù)是解釋執(zhí)行的每個仿真步都要回調(diào)一次 MATLAB 函數(shù)步長小、仿真時間長的時候慢得明顯比 C-MEX 版本慢一個數(shù)量級不夸張。自動調(diào)參要跑幾十上百次仿真這個開銷會直接翻倍。但它有三個好處在調(diào)參場景里權(quán)重很高。一是不用mex編譯改完公式保存就能跑調(diào)參過程中你隨時可能想改控制律C 文件編譯一遍再鏈接的反饋循環(huán)太長。二是出錯信息是 MATLAB 的堆棧哪一行越界、哪個維度對不上點(diǎn)進(jìn)去就能看不像 MEX 那樣只剩一句籠統(tǒng)的崩潰。三是它能直接調(diào)用工作區(qū)里的函數(shù)和參數(shù)結(jié)構(gòu)體寫代價函數(shù)、寫輔助邏輯不用重復(fù)實(shí)現(xiàn)。所以我的判斷標(biāo)準(zhǔn)很粗暴算法還在試、參數(shù)還在掃用 M 文件版算法定型、要交給別人跑長仿真再翻譯成 C-MEX 或者改成 MATLAB Function 塊。中間不要來回折騰先讓參數(shù)定下來。提示msfcn_開頭的函數(shù)名不是強(qiáng)制的但約定俗成sfundemos里的例子基本都是這個前綴排查問題時搜起來方便。3. S 函數(shù)本體拆解六個回調(diào)里哪些和調(diào)參直接相關(guān)3.1 setup 回調(diào)參數(shù)個數(shù)、采樣時間與狀態(tài)維度Level-2 M 文件 S 函數(shù)的結(jié)構(gòu)是固定的主函數(shù)同名里面調(diào)setupsetup負(fù)責(zé)聲明這個塊的輸入輸出、參數(shù)個數(shù)、狀態(tài)個數(shù)然后用RegBlockMethod把各個回調(diào)注冊進(jìn)去。自動調(diào)參要動的參數(shù)全部在NumDialogPrms里聲明。function msfcn_pid(block) % Level-2 M 文件 S 函數(shù)入口名字必須和文件名一致 setup(block); end function setup(block) % 兩個可調(diào)參數(shù)Kp、Ki一個誤差輸入一個控制輸出 block.NumDialogPrms 2; block.NumInputPorts 1; block.NumOutputPorts 1; % 輸入端口標(biāo)量誤差直通 block.InputPort(1).Dimensions 1; block.InputPort(1).DatatypeID 0; % double block.InputPort(1).Complexity Real; block.InputPort(1).DirectFeedthrough true; % 輸出依賴當(dāng)前輸入 % 輸出端口標(biāo)量控制量 block.OutputPort(1).Dimensions 1; block.OutputPort(1).DatatypeID 0; block.OutputPort(1).Complexity Real; % 一個連續(xù)狀態(tài)積分項(xiàng) block.NumContStates 1; block.SampleTimes [-1 0]; % 繼承上游采樣時間 % 注冊回調(diào)注意順序無所謂但名字必須對 block.RegBlockMethod(Start, Start); block.RegBlockMethod(Outputs, Outputs); block.RegBlockMethod(Derivatives, Derivatives); block.RegBlockMethod(Terminate, Terminate); endNumDialogPrms 2決定了模塊對話框里會出現(xiàn)兩個參數(shù)框多一個少一個都會在編譯階段報(bào)參數(shù)數(shù)量不匹配。DirectFeedthrough true是因?yàn)檩敵鲋苯佑昧水?dāng)前步的輸入誤差如果你的 S 函數(shù)輸出只依賴狀態(tài)這里要設(shè)成false否則會憑空造出代數(shù)環(huán)。SampleTimes [-1 0]表示繼承采樣時間調(diào)參時上游信號源的步長改了S 函數(shù)跟著變不用同步改兩處。連續(xù)狀態(tài)個數(shù)寫 1對應(yīng)積分項(xiàng)這個數(shù)字和后面Derivatives里賦值的維度必須一致。3.2 Outputs 與 Update把可調(diào)參數(shù)真正用起來Outputs每個仿真步都會被調(diào)用是控制律真正生效的地方。參數(shù)從block.DialogPrm(k).Data取第 k 個對應(yīng)對話框里第 k 個框索引從 1 開始這里最容易寫錯。function Outputs(block) % 每個仿真步計(jì)算控制量u Kp*e xi Kp block.DialogPrm(1).Data; Ki block.DialogPrm(2).Data; e block.InputPort(1).Data; xi block.ContStates.Data(1); u Kp * e Ki * xi; block.OutputPort(1).Data u; end function Derivatives(block) % 積分項(xiàng)導(dǎo)數(shù)dxi/dt e e block.InputPort(1).Data; block.Derivatives(1).Data e; end這里把積分增益放在Outputs里乘而不是在Derivatives里乘是為了讓 Kp、Ki 兩個參數(shù)在同一個函數(shù)里出現(xiàn)改公式的時候不容易漏。block.ContStates.Data(1)讀當(dāng)前狀態(tài)值block.Derivatives(1).Data寫導(dǎo)數(shù)兩者維度都必須是 1寫錯會報(bào)「derivative dimension mismatch」。參數(shù)讀取用DialogPrm(k).Data而不是DialogPrm(k)少寫.Data拿到的是結(jié)構(gòu)體參與運(yùn)算會直接報(bào)類型錯誤。如果你想在仿真過程中打印參數(shù)確認(rèn)腳本確實(shí)傳進(jìn)來了別用disp——每個步長打一行會把命令行刷爆。用persistent記一個首次標(biāo)志只在第一步打印一次function Outputs(block) persistent printed if isempty(printed) fprintf([sfunc] Kp%.4f, Ki%.4f\n, ... block.DialogPrm(1).Data, block.DialogPrm(2).Data); printed true; end % ... 后續(xù)控制律 endpersistent變量在兩次sim之間不會自動清空這一點(diǎn)在調(diào)參場景里是個隱患后面避坑章節(jié)會專門講。3.3 Start 與 Terminate每次仿真前的狀態(tài)復(fù)位自動調(diào)參要跑幾十次仿真每次都必須是干凈起點(diǎn)否則第二輪繼承第一輪的積分器殘值結(jié)果沒有可比性。Start回調(diào)就是干這個的。function Start(block) % 每次仿真開始把連續(xù)狀態(tài)清零 block.ContStates.Data 0; end function Terminate(block) % 仿真結(jié)束時的收尾記錄、清理、關(guān)文件 fprintf([sfunc] 本次仿真結(jié)束\n); endStart里給ContStates.Data賦初值等價于在連續(xù)系統(tǒng)里設(shè)初始條件。不寫這一句Simulink 會用上一次的值或者默認(rèn)零短仿真看不出差別長仿真和多次連續(xù)調(diào)用時會出現(xiàn)莫名其妙的漂移。Terminate里不要做重活打印一行、關(guān)一個文件句柄就夠了它會在每次仿真結(jié)束時被調(diào)用寫太多會拖慢整體掃參速度。有人喜歡在Start里用assignin往外寫變量做記錄這個習(xí)慣要克制。S 函數(shù)執(zhí)行在仿真引擎內(nèi)部往里塞副作用容易和并行仿真沖突記錄的事情交給外層的cost_once更穩(wěn)妥。3.4 用 block 參數(shù)做在線可調(diào)的入口除了對話框參數(shù)Level-2 S 函數(shù)還支持運(yùn)行時參數(shù)Run-time Parameter注冊之后可以在仿真運(yùn)行過程中用set_param改不用重啟仿真。做參數(shù)掃描時用不上但做「參數(shù)自適應(yīng)」或者「手動拖滑塊看響應(yīng)」的場景很有用。function setup(block) block.NumDialogPrms 2; % 把第一個參數(shù)注冊成運(yùn)行時參數(shù)可在仿真中修改 block.RegBlockMethod(PostPropagationSetup, DoPostPropSetup); end function DoPostPropSetup(block) block.NumDworks 1; block.Dwork(1).Name KpRt; block.Dwork(1).Dimensions 1; block.Dwork(1).DatatypeID 0; block.Dwork(1).Complexity Real; block.Dwork(1).UsedAsDiscState true; end這段是給你留個口子真要做在線調(diào)參把Kp從對話框搬到運(yùn)行時參數(shù)外部用set_param在仿真中途改值就能看到響應(yīng)實(shí)時變化。代價是代碼復(fù)雜度上去一截而且運(yùn)行時參數(shù)在代碼生成里支持有限。我一般只在演示或者交互式調(diào)試時用批量掃參還是走對話框參數(shù)那條路邏輯簡單、可復(fù)現(xiàn)。4. 自動調(diào)參引擎把 sim 命令包進(jìn)一個可迭代的目標(biāo)函數(shù)4.1 目標(biāo)函數(shù)一次仿真返回一個標(biāo)量代價搜索器只認(rèn)一個東西輸入一組參數(shù)輸出一個越小越好的標(biāo)量。所以第一件事是把「跑仿真 算指標(biāo)」封成一個函數(shù)中間過程全部藏在里面。function J cost_once(p, cfg) % p : 候選參數(shù) [Kp, Ki] % cfg : 配置結(jié)構(gòu)體含模型名、仿真時長、上下界、懲罰值 % J : 標(biāo)量代價越小越好 % 越界直接給懲罰不去跑仿真省時間 if any(p cfg.lb) || any(p cfg.ub) J cfg.penalty; return; end in Simulink.SimulationInput(cfg.model); in in.setVariable(Kp, p(1)); in in.setVariable(Ki, p(2)); in in.setModelParameter(StopTime, num2str(cfg.tStop)); in in.setModelParameter(SimulationMode, normal); in in.setModelParameter(SaveFormat, Structure with time); in in.setModelParameter(SaveOutput, on); in in.setModelParameter(OutputSaveName, yout); try out sim(in); catch ME % 仿真炸了也給懲罰但把原因記下來 fprintf([cost] 仿真失敗%s\n, ME.message); J cfg.penalty; return; end y out.get(yout); t y.time; sig y.signals.values; ref cfg.ref; % 參考信號標(biāo)量或同長度向量 e ref - sig; J trapz(t, abs(e)); % IAE 指標(biāo) J J cfg.wOvershoot * max(0, max(sig) - max(ref)); % 超調(diào)懲罰 endsetVariable綁定的變量只在這一次仿真里有效跑完自動釋放不會污染基礎(chǔ)工作區(qū)這是它比assignin干凈的地方。SimulationMode必須顯式設(shè)成normalM 文件 S 函數(shù)在加速模式下不支持不設(shè)的話模型上次保存成 Accelerator 就會直接報(bào)錯。try/catch里返回懲罰值而不是拋出去是因?yàn)樗阉髌饔龅揭淮问【椭袛嗵速M(fèi)給個大代價讓它自己繞開這組參數(shù)更劃算。指標(biāo)用trapz算 IAE比簡單求和更貼近連續(xù)系統(tǒng)的定義。超調(diào)懲罰單獨(dú)加權(quán)權(quán)重wOvershoot放在配置里方便你在「快但抖」和「慢但穩(wěn)」之間調(diào)傾向。4.2 主循環(huán)網(wǎng)格掃描與局部搜索的組合純網(wǎng)格掃得全但慢純梯度搜索快但容易掉進(jìn)局部極小??刂茀?shù)這類問題我一般先用粗網(wǎng)格框出大致區(qū)域再用fminsearch在局部細(xì)化兩段式比單用一種穩(wěn)。function best tune_main() cfg struct(model, loop_model, tStop, 10, ref, 1, ... lb, [0 0], ub, [10 3], penalty, 1e6, ... wOvershoot, 2); KpGrid linspace(0.2, 5.0, 8); KiGrid linspace(0.0, 2.0, 6); records zeros(numel(KpGrid) * numel(KiGrid), 3); % Kp, Ki, J idx 0; best struct(J, inf, p, [NaN NaN]); for kp KpGrid for ki KiGrid idx idx 1; p [kp ki]; J cost_once(p, cfg); records(idx, :) [kp ki J]; if J best.J best.J J; best.p p; fprintf([grid] 新最優(yōu) J%.4f Kp%.3f, Ki%.3f\n, J, kp, ki); end end end % 局部細(xì)化從網(wǎng)格最優(yōu)點(diǎn)出發(fā) opts optimset(Display, iter, TolX, 1e-3, MaxIter, 40); pFine fminsearch((p) cost_once(p, cfg), best.p, opts); JFine cost_once(pFine, cfg); if JFine best.J best.p pFine; best.J JFine; end save_result(records, best, cfg); end網(wǎng)格那段是雙循環(huán)每個組合調(diào)一次cost_oncerecords矩陣把三元組存下來最后一起落盤。fminsearch從網(wǎng)格最優(yōu)點(diǎn)出發(fā)TolX控制參數(shù)收斂精度MaxIter防止它在一個平坦區(qū)域里磨太久。注意fminsearch本身不管邊界越界的情況靠cost_once開頭的懲罰兜住——這也是為什么懲罰值要給得足夠大太小的話搜索器會心安理得地待在界外。4.3 接入 fminsearch / ga 的寫法與邊界約束fminsearch夠用但它對初值敏感、不支持約束。參數(shù)維度上去了、或者你明確知道 Kp 和 Ki 有耦合關(guān)系換遺傳算法或者模式搜索更合適。接口是一模一樣的都是「輸入向量、輸出標(biāo)量」。% 方案一模式搜索自帶邊界約束 opts optimoptions(patternsearch, Display, iter, ... MaxIterations, 60, MeshTolerance, 1e-3); [pOpt, JOpt] patternsearch((p) cost_once(p, cfg), best.p, ... [], [], [], [], cfg.lb, cfg.ub, [], opts); % 方案二遺傳算法適合多峰、參數(shù)多于 3 個的情況 opts optimoptions(ga, Display, iter, PopulationSize, 20, ... MaxGenerations, 30, UseParallel, false); [pOpt, JOpt] ga((p) cost_once(p, cfg), 2, ... [], [], [], [], cfg.lb, cfg.ub, [], opts);patternsearch的好處是邊界直接作為參數(shù)傳進(jìn)去不用靠懲罰收斂過程更干凈代價是每輪要評估的點(diǎn)數(shù)比fminsearch多。ga適合參數(shù)多、代價函數(shù)有多個局部極小的情況但它的隨機(jī)性意味著兩次跑出來的結(jié)果可能不一樣做實(shí)驗(yàn)記錄的時候要把隨機(jī)種子固定下來否則復(fù)現(xiàn)不了。UseParallel那一項(xiàng)先關(guān)掉等單機(jī)流程跑通了再打開不然出錯時你分不清是并行的問題還是代價函數(shù)的問題。4.4 結(jié)果落盤與最優(yōu)參數(shù)回寫掃完一輪結(jié)果要能查、能對比、能復(fù)現(xiàn)。save_result干三件事存.mat方便下次直接load寫.csv方便丟進(jìn)表格里看趨勢把最優(yōu)參數(shù)寫回基礎(chǔ)工作區(qū)方便立刻手動驗(yàn)證。function save_result(records, best, cfg) stamp datestr(now, yyyymmdd_HHMM); matFile fullfile(results, [run_ stamp .mat]); csvFile fullfile(results, [run_ stamp .csv]); save(matFile, records, best, cfg); fid fopen(csvFile, w); fprintf(fid, Kp,Ki,J\n); for k 1:size(records, 1) fprintf(fid, %.6f,%.6f,%.6f\n, records(k, 1), records(k, 2), records(k, 3)); end fclose(fid); % 回寫最優(yōu)值方便立刻手動跑一次確認(rèn) assignin(base, Kp, best.p(1)); assignin(base, Ki, best.p(2)); fprintf([save] 最優(yōu) J%.4f, Kp%.4f, Ki%.4f\n, best.J, best.p(1), best.p(2)); end文件名帶時間戳跑一百輪也不會互相覆蓋。.mat里存了cfg下次想復(fù)現(xiàn)同一組實(shí)驗(yàn)把cfg取出來直接用不用回憶當(dāng)時設(shè)的仿真時長和懲罰權(quán)重。最后那兩行assignin是給手動驗(yàn)證留的口子模型對話框里填的是變量名Kp、Ki腳本跑完工作區(qū)里就是最優(yōu)值打開模型點(diǎn)一次運(yùn)行就能看響應(yīng)。5. 避坑與排查五類讓調(diào)參結(jié)果不可信的坑5.1 參數(shù)個數(shù)不匹配S 函數(shù)在編譯階段就掛現(xiàn)象很直接點(diǎn)運(yùn)行命令行報(bào)S-function msfcn_pid has fewer parameters in its dialog than it expects或者反過來多了一個。原因通常是setup里NumDialogPrms改了但模型里那個 S-Function 模塊的對話框沒同步改兩個地方各說各話。解決方法是把「聲明」和「實(shí)例」當(dāng)成一對強(qiáng)耦合的東西來維護(hù)。改NumDialogPrms之后一定打開模型確認(rèn)參數(shù)框數(shù)量和順序順序錯了不會報(bào)錯但 Kp 會讀到 Ki 的值跑出來的結(jié)果看著像那么回事其實(shí)全錯。我一般會在setup里加一段自檢參數(shù)個數(shù)不對時直接error并打印期望值比等到仿真跑一半再發(fā)現(xiàn)強(qiáng)。function setup(block) block.NumDialogPrms 2; if numel(block.DialogPrm) ~ 2 error(msfcn_pid:prmCount, ... 期望 2 個參數(shù)實(shí)際收到 %d 個, numel(block.DialogPrm)); end end5.2 加速模式與 M 文件 S 函數(shù)不兼容現(xiàn)象是仿真剛啟動就報(bào)M-file S-function cannot be used in accelerated mode或者切到 Accelerator 之后模型直接編譯失敗。原因是 Level-2 M 文件 S 函數(shù)是解釋執(zhí)行的加速模式要把它編譯成目標(biāo)代碼兩者天然沖突。解決方式只有兩條要么在調(diào)參腳本里強(qiáng)制SimulationMode為normal要么把邏輯改寫成 MATLAB Function 塊或 C-MEX。前一條是調(diào)參階段的默認(rèn)選擇寫進(jìn)cost_once里就不會忘。真正要跑長仿真、要加速的時候說明參數(shù)已經(jīng)定得差不多了那時候再做移植順序不要反。5.3 狀態(tài)未復(fù)位第二輪結(jié)果開始漂移這個坑最陰。第一輪仿真結(jié)果正常從第二輪開始響應(yīng)曲線整體偏移一點(diǎn)越往后偏得越多但腳本、參數(shù)、模型一個都沒改。原因有兩類一類是Start里沒給ContStates.Data賦初值積分器繼承了上一輪的值另一類是 S 函數(shù)里用了persistent變量做計(jì)數(shù)或緩存兩次sim之間不會被清空。排查方法是把同一組參數(shù)連續(xù)跑三次比較三次的輸出是否逐位相同。不相同就順著persistent關(guān)鍵字和ContStates找。解決上狀態(tài)一律在Start里顯式清零persistent能不用就不用非要用就在Start里重置function Start(block) block.ContStates.Data 0; clear msfcn_pid % 清掉本函數(shù)內(nèi)的 persistent end5.4 數(shù)據(jù)記錄格式選錯指標(biāo)算出來是錯的現(xiàn)象是從out里取出來的yout維度不對signals.values是雙列或者時間向量長度和信號長度對不上trapz算出來的 IAE 數(shù)量級明顯離譜。原因是SaveFormat設(shè)成了Dataset或者Array讀取方式和Structure with time完全不同。兩種讀法要分清楚Structure with time用y.time和y.signals.valuesDataset用y.get(信號名).Values.Data和.Time。調(diào)參腳本里統(tǒng)一用一種別一半腳本用這種一半用那種。我習(xí)慣在cost_once開頭就把SaveFormat釘死讀的時候加一句維度斷言assert(size(sig, 2) 1, yout 維度異常檢查 SaveFormat 設(shè)置);5.5 路徑與命名沖突現(xiàn)象是Undefined function msfcn_pid或者改完文件內(nèi)容運(yùn)行結(jié)果不變。原因通常有三個函數(shù)名和文件名不一致Level-2 M 文件 S 函數(shù)要求嚴(yán)格同名文件所在目錄沒加進(jìn)搜索路徑工作區(qū)里存在同名變量遮蔽了函數(shù)。排查順序是which msfcn_pid -all看有幾個同名文件、path看目錄在不在、whos msfcn_pid看有沒有變量撞名。這三個命令跑一遍九成問題當(dāng)場現(xiàn)形。工程里我固定用init_project統(tǒng)一加路徑就是為了把這類問題擋在入口。6. 二次開發(fā)把單機(jī)掃參改成可復(fù)用的調(diào)參流水線單機(jī)腳本跑通之后最值得做的改造是并行。cost_once之間互不依賴把網(wǎng)格那層雙循環(huán)換成parfor或者直接用parsim批量提交SimulationInput數(shù)組四核機(jī)器上差不多能壓到四分之一時間。改的時候注意兩件事cost_once里不能有對基礎(chǔ)工作區(qū)的寫操作SimulationInput要提前構(gòu)造成數(shù)組再一次性提交否則并行收益會被調(diào)度開銷吃掉。第二個改造方向是把代價函數(shù)從單目標(biāo)擴(kuò)成多目標(biāo)比如同時看調(diào)節(jié)時間和超調(diào)量用加權(quán)和或者 Pareto 前沿。加權(quán)系數(shù)放在cfg里別硬編碼在函數(shù)內(nèi)部否則每換一個工況就要改代碼。第三個方向是把最優(yōu)參數(shù)自動回寫到模塊的 mask 或者數(shù)據(jù)字典里形成「掃參—定參—固化」的閉環(huán)。這一步我建議保留人工確認(rèn)環(huán)節(jié)腳本只負(fù)責(zé)把候選值和對應(yīng)的響應(yīng)曲線存成圖最終寫不寫回由人拍板。自動寫回看著省事一旦代價函數(shù)設(shè)計(jì)得有偏固化的就是一組錯參數(shù)。驗(yàn)證方法上我固定做三件事拿最優(yōu)參數(shù)手動跑一次和腳本記錄的最優(yōu)代價對比誤差超過百分之一就說明記錄鏈路有問題換一組沒參與搜索的工況參數(shù)跑一次看泛化把隨機(jī)種子固定下來重復(fù)跑一遍結(jié)果應(yīng)當(dāng)逐位一致不一致就回去查persistent和狀態(tài)復(fù)位。有一回我圖省事把cost_once里的SimulationMode那行刪了想著模型默認(rèn)就是 normal結(jié)果模型被同事存成了 Accelerator一整晚的掃參全部作廢最優(yōu)值是一組隨機(jī)數(shù)。從那以后我每次開新工程都強(qiáng)制先跑一遍init_project加三次重復(fù)性自檢確認(rèn)三次輸出完全一致再開始掃參。希望這套流程能幫到你少走一遍我踩過的彎路。本文還有配套的精品資源點(diǎn)擊獲取