我手头这个项目是做一款电磁执行机构的结构优化,尺寸参数一共五个:永磁体厚度、气隙长度、线圈匝数、齿宽和磁轭厚度。三个性能指标天生互斥,气隙磁密要拉高、总质量要压低、磁密波动还要小。刚开始我用最原始的手动调参,一天最多试五六组方案,到第20组已经彻底分不清是哪个参数在起作用。后来我把MATLAB里的NSGA-II算法和Maxwell电磁仿真串成一条全自动优化链路,让算法自己去搜参数空间,一个晚上自动评估几百上千组设计点,最终把整条Pareto前沿摆在我面前。这篇就是我搭这套流程的完整复盘,覆盖整体架构、五变量数学建模、Maxwell脚本化改造、MATLAB主控代码、两种实时交互方式对比,以及提速和踩坑经验。正在做电磁器件结构优化、或者想把外部优化算法接进仿真软件的朋友,可以直接把这篇文章当施工图来用。
1. 从手动调参到自动寻优:这套方案到底解决了什么问题
1.1 五变量手工调参的真实痛点
五个参数看着不多,但真正动手调过的人都知道有多恶心。每个结构参数牵连的性能少则两个、多则三个,而且全是非线性变化。加厚永磁体,气隙磁密确实上去了,代价是重量增加、漏磁路径变宽;缩小气隙,磁密也跟着上,但加工公差、装配难度立马敏感起来。最要命的是参数之间互相耦合——磁钢加到某个厚度之后磁路开始饱和,这时候你再调气隙,响应规律和薄磁钢时完全不同。你盯着上一次的结论往下顺,很容易在局部组合里打转。
组合数量更是反直觉。五个变量,每个只取五个有区分度的水平,全因子扫描就是 5^5=3125 组方案。就算一组仿真只要20秒,连续跑也要17个小时还多,这还没算每次改参数、改模型、看结果的人工时间。实际上绝大多数工程师不会做全因子扫描,而是靠经验挑几个点试,这就回到了"赌手感"的路子上。手动试凑本质上是一个串行贪心搜索:先固定四个参数,试第五个;试完第五个,回头再调第一个,发现第一个的最优区间又变了。这种贪心省略掉的交互效应,恰恰是电磁结构设计里最值钱的信息。
1.2 为什么是NSGA-II而不是单目标优化
有人会问:用遗传算法跑单目标不行吗?把三个目标加权加起来成一个综合指标,用MATLAB自带ga函数照样能跑。理论上可以,但"权重"这东西要先拍脑袋定。磁密和重量怎么算权重?波动率重要0.3还是0.7?不同产品定位、不同客户需求,答案完全不同。你定一个权重,算法只会还给你一个点,这个点是不是真正值得制造的折中方案,并不知道。你想要的是一条权衡曲线,让决策者带着业务约束去曲线上挑,而不是提前赌一把权重。
NSGA-II正是干这个的。它是经典的多目标进化算法,核心机制一句话能说清:每一代里,它先把种群按照支配关系分层——不被任何其他个体支配的那些解构成第一层,也就是当前代的Pareto前沿;然后再算同一层里每个个体的拥挤距离,越孤立的解越被优先保留,避免所有解挤到某一个小区域;最后父亲解合并进子代种群做精英保留,好解不会因为变异交叉而弄丢。把它想象成一个打分机制:既看方案是否在全局层面占优,也看方案之间是否足够"分散",还保证历代最好的点永远有复活机会。
MATLAB里直接用gamultiobj就行,它是工具箱对NSGA-II的实现,虽然内部默认算子做了调整,但非支配排序、拥挤距离、精英保留这些主干都在,拿来接Maxwell完全够用。真要是想要教科书版的SBX交叉和多项式变异,也可以自己写或者用第三方实现,但工程项目的投入产出比不划算,内置版本我实测下来收敛性和分布性都不差。
1.3 整体架构:MATLAB出题、Maxwell做题、文件对答案
整套系统我总结成一句话:MATLAB负责出题和批改,Maxwell负责做题,JSON和CSV文件负责传纸条。MATLAB端跑的是NSGA-II主循环,每产生一代种群,就把这代所有个体的五个变量写成一个JSON参数文件;然后通过命令行启动Maxwell的批处理脚本,让它在自己的环境里逐个更新模型参数、跑电磁场求解、导出气隙磁密曲线;等Maxwell全部算完,写一个完成标记文件;MATLAB轮询到标记之后读回结果文件,统计出三个目标值,喂给NSGA-II生成下一代。循环往复,直到收敛。
很多人想象的"实时交互"是MATLAB和Maxwell之间建一条像点对点通信那样的通道,参数实时传过去,结果实时传回来。实际工程里更高效的做法是以"代"为单位的批量交互:一代30个个体一次性交给Maxwell,进程启动一次,内部连续算完30个设计点,再一起把结果传回。这么做有两个直接好处:一是避开每次启动Electronics Desktop都要等30到60秒冷启动的成本,二是让Maxwell脚本内部可以批量复用同一个项目文件,避免反复开关工程的额外开销。这套流程跑顺之后,真正的瓶颈就只剩单次仿真耗时和代数预算,下面按模块拆开讲。
2. 五变量优化问题的数学建模:边界、目标与约束
2.1 五个结构变量怎么定义
这一节先明确优化问题的数学外壳。任何优化算法都只认"一组数进去,一组数出来",所以第一步就是把物理结构抽象成变量、边界和目标。
我用的五个变量定义如下:
| 变量名 | 物理含义 | 单位 | 下限 | 上限 | 边界依据 |
|---|---|---|---|---|---|
| x1 | 永磁体厚度 | mm | 3 | 8 | 太薄励磁不足,太厚成本失控、磁路饱和 |
| x2 | 气隙长度 | mm | 0.5 | 2 | 下限受装配公差和同心度限制 |
| x3 | 线圈匝数 | 匝 | 50 | 200 | 由允许电流密度和窗口面积共同约束 |
| x4 | 齿/极靴宽度 | mm | 5 | 15 | 决定磁通路径的有效面积 |
| x5 | 磁轭厚度 | mm | 3 | 10 | 太薄铁芯饱和,太厚体积浪费 |
边界不是随便拍的。气隙下限0.5mm是因为实际装配时内外同心度误差、温升变形都摆在那,标称值设得再小,产线上装不出来也是白搭。磁轭厚度上限10mm是因为磁路截面积超过需求后,厚度再翻倍只会拖累重量,B值早就不涨了。边界设计本身就能缩小搜索空间,省下不少仿真量。
注意x3匝数有个特殊性:NSGA-II默认在连续空间里搜索,但匝数必须是整数。我的做法是在适应度函数内部做round(x3),仿真和结果统计都用舍入后的值。这样算法虽在连续域探索,但实际评估的都是合法整数,Pareto前沿上会出现轻微离散感,不影响决策。
2.2 三个互相打架的优化目标
这个案例里我设了三个目标函数,分别对应性能、成本和品质:
- f1 = 气隙中心路径上的磁通密度峰值,单位T,要求最大化;
- f2 = 有效结构总质量,单位kg,要求最小化;
- f3 = 气隙磁密沿路径分布的波动率,定义为标准差除以均值,要求最小化。
f1对应电磁出力水平,磁密越高,相同安匝下输出力越大。f2对应材料成本,磁钢、铁芯、线圈的密度和体积都可以用简化几何算出来。f3对应静力平稳性,磁密波动大,意味着在不同工作点下力的波动也大,对控制精度不友好。
三个目标天然冲突。想把f1做高,就得多放磁钢、收紧气隙,f2立刻恶化;气隙一收紧,磁密波形又常常变得更尖,f3也会变差。想要波动小,往往需要更宽的极靴或更均匀的磁路,体积重量又上去了。因为不存在一个点同时制霸三个目标,所以才需要用NSGA-II去生成Pareto前沿,让这三个指标的取舍关系一目了然地摆在决策者面前。
2.3 目标归一化与约束处理
gamultiobj在做非支配排序时,是逐个目标独立比较的,理论上不需要把所有目标拉到一个量纲。但拥挤距离计算时,如果某个目标的数量级远大于其他目标,那个方向上的距离就会被稀释,Pareto前沿在三维空间里容易挤成一片。我的经验是:从Maxwell导出原始结果后,先对三个目标做min-max归一化,再返回给优化器。归一化不会改变支配关系,但能让拥挤距离的分布更均匀,前沿点在空间里铺得更开。
范围怎么定?每一代都用当前代种群里的最小值当0、最大值当1,这样就不需要预设全局先验。目标函数里f1是最大化,gamultiobj默认全部最小化,所以返回给优化器的是-f1,最后画图再取回来。任何在这个环节忘了加负号的人,都会跑出一代比一代更差的磁密,算是这个流程里最典型的低级坑之一。
约束方面,本案例没有硬约束,变量的上下边界本身就是约束,交给gamultiobj的lb和ub参数就行。如果后面要加"总质量不超过5kg"这种约束,可以写非线性约束函数c(x)传给优化器。但要提醒一点:约束值如果依赖Maxwell仿真结果,每一次约束评估同样要触发一次仿真,等于是额外烧时间,能省则省。能用边界近似的就不要再画蛇添足。
3. Maxwell侧脚本化改造:从GUI手工操作到参数驱动
3.1 先用录制脚本搭起自动化骨架
Maxwell能被人从外部指挥,靠的是一套基于IronPython的脚本机制。最笨也最稳的起步方式是打开Tools菜单里的Record Script,手动完整操作一遍项目:打开aedt工程、激活设计、把某个参数从5mm改成8mm、跑一次求解、导出气隙上的磁场强度报告。你在界面上做的每一个动作,都会被录成一段Python风格的脚本代码,这就是自动化骨架。
录出来的脚本通常带一堆GUI控制语句,比如oDesktop.RestoreWindow()、各种对话框确认、视图操作,这些不是每次都需要,要先删掉。真正有用的动作序列其实只有四件事:打开项目/激活设计、更新设计变量、运行分析设置、导出结果。把这段瘦身后的脚本放进一个函数里,就是Maxwell侧的"做题函数"。
需要提醒的是,不同大版本的Maxwell脚本API关键字有差异,比如ChangeProperty的传参结构、CreateReport的参数表头在不同版本里都可能改名。不要指望网上抄一段就能跨版本跑通,正确姿势是自己录一段、看一眼生成代码、再删改。我把"以录制脚本为准"当成铁律之后,再没被版本迁移坑过。
3.2 参数下发:把一代种群的五变量写进设计表
MATLAB和Maxwell之间传参数,我用JSON文件做中间人。MATLAB先把一代种群写成一个JSON数组,每个元素是一个五变量字典,形如:
[ {"x1": 5.2, "x2": 1.1, "x3": 120, "x4": 8.5, "x5": 6.2}, {"x1": 6.0, "x2": 0.8, "x3": 150, "x4": 9.0, "x5": 5.5} ]Maxwell侧脚本逐条读取,然后调用设计对象的属性修改接口,把参数写进变量表。结构大概是:
import json, os def set_design_variables(design, p): new_props = [] for name, val in p.items(): if name.startswith("x"): new_props.append(["NAME:" + name, "Value:=", str(val) + "mm"]) design.ChangeProperty( "NAME:AllTabs", ["NAME:LocalVariableTab", ["NAME:PropServers", "LocalVariables"], ["NAME:NewProps"] + new_props])有两个细节必须提前处理好。第一,变量x1到x5要事先在Maxwell模型里建好,脚本只改Value,不负责新建变量,不然同名变量重复添加会直接报错。第二,单位别乱写,Maxwell表达式里的"5mm"和"5 mm"都合法,但字符串拼接时少一个空格最保险,我统一用str(val) + "mm"。
3.3 批量求解、导出结果和完成标记
参数一变,Maxwell会重新剖分网格,然后执行求解。脚本里循环遍历整个种群,每处理一个个体就调用一次分析设置:
results_path = r"C:\work\optim_results" design.Analyze("Setup1") # Setup1 是求解设置名求解完成后,需要导出气隙中心路径上的磁密数据。我让Maxwell生成一个沿气隙的Line报告,把位置和B值两列数据导成CSV。IronPython环境很受限,没有numpy、pandas,所以我让脚本只负责导出原始曲线文件,不做统计计算。峰值、波动率、质量估算全部回到MATLAB算,这样两边各干各的强项。
最后一件事是写完成标记。整个种群跑完,脚本在指定目录生成一个done_XXXX.flag文件,里面随便写个OK。MATLAB看到这个文件就认为这批结果齐了,可以拿走。为什么不用脚本退出事件做同步?因为Electronics Desktop在批处理时经常派生子进程,主进程退出不代表求解线程全部收工,直接读结果文件容易读到半截数据。flag文件是最后一道闸门,简单、可靠、不挑版本。
4. MATLAB主控程序与NSGA-II核心代码
4.1 gamultiobj的关键选项
MATLAB侧主程序比较短,核心是gamultiobj的参数设置和适应度函数。我用到的关键选项如下:
| 参数名 | 推荐值 | 说明 |
|---|---|---|
| PopulationSize | 30 | 五变量问题30起步,仿真时间宽裕可以上50 |
| MaxGenerations | 50 | 具体看收敛曲线,我通常先给50代 |
| ParetoFraction | 0.35 | 精英前沿个体比例,太高容易退化 |
| UseVectorized | true | 一次传入整代种群,才能配合批量仿真 |
| Display | iter | 迭代过程显示在命令行,方便盯进度 |
| FunctionTolerance | 1e-4 | 收敛判断阈值,防止提前退出 |
UseVectorized是这套方案能否高效运转的关键,没有之一。打开后,适应度函数接收的不再是单个个体向量,而是整个种群矩阵,也就是一次给了你30组五变量。正常情况下你拿到这30组数,就可以一次性写进JSON、一次性启动Maxwell、一次性拿回30组目标值。如果不开向量化,gamultiobj会逐个体调用适应度函数,每调一次都要启动一遍Maxwell进程,30个个体的种群等于一天里重启30次Electronics Desktop,时间全耗在冷启动上。
4.2 向量化适应度函数:一代只启动一次Maxwell
向量化适应度函数的核心骨架如下:
function fpop = maxwell_fitness_vect(xpop) % xpop: popSize x 5 矩阵,每一行是一个个体 global CURRENT_GEN; genLabel = sprintf('%04d', CURRENT_GEN); % 1) 写这一代所有个体的参数文件 write_pop_params(xpop, ['params_' genLabel '.json']); % 2) 启动Maxwell批处理跑完这一代 cmd = sprintf('"%s" -RunScriptAndExit "%s" -Batch', ... AEDT_EXE, RUN_SCRIPT); system(cmd); % 3) 等待完成标记 wait_until_file_exists(['done_' genLabel '.flag'], 1800); % 4) 读回所有个体的CSV曲线并统计三目标 fpop = compute_objectives(xpop, genLabel); % 5) 清理临时文件 delete(['done_' genLabel '.flag']); delete(['params_' genLabel '.json']); end这里AEDT_EXE是Electronics Desktop可执行文件的完整路径,RUN_SCRIPT是Maxwell侧Python脚本的路径。这里有个很实用的技巧:用全局变量CURRENT_GEN为每一代生成不同的文件名标签,从根上避免"上一代残留结果文件被下一代误读"的经典问题。如果你不太习惯全局变量,也可以在外面用时间戳datestr(now, 'HHMMSS')拼进文件名,效果一样。
compute_objectives函数的实现思路是按个体读取CSV:
function fpop = compute_objectives(xpop, genLabel) nPop = size(xpop, 1); fpop = zeros(nPop, 3); for i = 1:nPop csvFile = sprintf('bcurve_p%d_%s.csv', i, genLabel); data = readmatrix(csvFile); B = data(:, 2); fpop(i, 1) = -max(B); % 最大化磁密 -> 取负 fpop(i, 2) = estimate_mass(xpop(i,:)); % 密度+体积估算 fpop(i, 3) = std(B) / mean(B); % 波动率 end end别忘了gamultiobj默认最小化所有目标,所以磁密这种"越大越好"的指标必须取负号。把这行忘掉的人都会得到一条从高磁密往低磁密跑的Pareto前沿,而且可能跑完整个优化周期才发现,返工成本极高。
4.3 进程同步的那些细节
进程同步我采用了"轮询flag文件"的办法:
function wait_until_file_exists(flagFile, timeoutSec) tic; while ~exist(flagFile, 'file') if toc > timeoutSec error('Maxwell批处理超时,检查脚本是否卡死'); end pause(2); end end超时时间建议设到单代总耗时的两倍以上,否则偶尔网格剖分或许可证排队时间一长,MATLAB会误判超时,整批结果白跑。我一般设1800秒,即30分钟。
还有一个容易被忽视的细节:system(cmd)返回后,Maxwell进程未必完全退出。Windows下Electronics Desktop经常派生后台子进程,主界面关了,求解进程还挂着,你的结果文件其实已经写好了,但进程没退干净。所以不要用system的返回状态来判断数据何时就绪,也不要在system之后立刻读结果,老老实实等flag。每代算完后,还要手动清理临时文件,否则几千个CSV堆在一个目录里,机械盘越跑越慢,最终拖垮整个流程。
5. 两种数据实时交互方式实测对比
5.1 批处理模式:稳如老狗
主推的就是批处理模式。MATLAB通过system调用Electronics Desktop,命令行里带着脚本路径和批量参数即可:
"C:\Program Files\AnsysEM\v231\Win64\ansysedt.exe" -RunScriptAndExit "C:\work\run_optim.py" -Batch-Batch参数很关键,它让程序不弹图形界面、不走交互初始化,直接跑完脚本退出。视觉上你看到的只是一个黑框窗口闪几次,但运行效率和不稳定性都比GUI模式好太多。脚本跑完之后,Matlab通过flag文件拿结果,整个链路是单向、顺序、无回环的,几乎没有状态竞争。
缺点前面也说了:每代都要冷启动一次Electronics Desktop,许可证加载、工程打开、网格缓存初始化这些步骤重复发生。实测冷启动时间一般在30到60秒,50代就是25到50分钟,这笔账不算小,但它用稳定性换回来了。批处理模式下,版本升级只要脚本里API不变,基本不会出幺蛾子。
5.2 COM常驻模式:实时交互的另一种可能
如果想做到字面意义上的"实时交互",也就是让Maxwell进程常驻、随叫随到,可以用MATLAB的COM接口直接拉起Electronics Desktop,反复执行脚本:
hDesktop = actxserver('Ansoft.ElectronicsDesktop'); hDesktop.Invoke('ExecuteScript', 'C:\work\script_run_one.py'); % 等结果,继续下一次调用 hDesktop.Invoke('ExecuteScript', 'C:\work\script_run_two.py'); hDesktop.release();这种模式启动一次进程,后面每次调用都只是把Python脚本丢进常驻进程执行,省掉了反复冷启动的几十秒。调试的时候特别方便,你可以一个个体一个个体地试,在GUI里看着它跑,确定结果没问题再继续改下一个。它还有个好处,就是MATLAB可以按需动态决定下一步传什么参数,而不是把一整代固化成一个文件。
但COM方式对版本和运行环境比较挑剔。ProgID在不同版本里可能不一样,类型库没注册好直接actxserver创建失败;64位MATLAB和32位组件之间也有位数匹配问题;频繁跨进程调用还可能把桌面进程搞崩,一旦崩了,你只会看到一个孤立无援的进程卡在那。我这边的实测是,跑短脚本、做调试它很舒服,跑全流程优化稳定性稍微差一点。
5.3 交互通道的工程选型建议
做个直观对比:
| 对比项 | 批处理模式 | COM常驻模式 |
|---|---|---|
| 稳定性 | 高 | 中 |
| 单次冷启动开销 | 30-60秒/代 | 仅首次 |
| 实时交互能力 | 弱 | 强 |
| 实现复杂度 | 低 | 中高 |
| 版本兼容性 | 稳定 | 较敏感 |
| 适用场景 | 批量优化主流程 | 调试、逐个体验证 |
我的建议是:大批量优化任务用批处理,在它外面套一个flag同步机制,这是绝对不会翻车的组合;COM常驻留给调试期,需要盯着某个个体在Maxwell里的场图、判读结果的时候,用它非常顺手。要是你非要在优化主流程里用COM,建议加一个看门狗,超时后强制杀进程并把该代标记为失败,避免整个优化挂死。
6. 提速技巧与真实踩坑记录
6.1 先算账:一次完整优化要多长时间
很多人在文章开头就被劝退,是因为没先算时间账。每次求解时间、种群规模、代数,三者相乘就是总时长。拿我的案例来说,二维静磁场求解,单个体大约20秒,加上结果导出3秒,算25秒一个设计点;种群30,代50,纯求解时间就是25×30×50=37500秒,约10.4小时。再加上每次代启动Electronics Desktop的冷启动时间约45秒,50代额外加了37分钟。总数大概11小时,一夜跑完很舒服。
如果你换成三维静磁场,单个体时间轻松上到2到5分钟,同样的种群和代数,总时长直接飙到50到100小时,这就基本没法做了。所以开工之前先测一个个体到底要多久,然后用这个公式估算总时长,再决定是砍代数、砍种群,或者是把模型降维。
时间没预算,后面所有优化策略都是空谈。
6.2 提速三板斧:2D、对称、放宽收敛
第一板斧是降维。很多电磁结构沿轴向是拉伸的,端部效应不大,用二维平面模型就能算得比较准。二维静磁场求解速度和三维完全不是一个量级,同等精度下一般快5到20倍。先老老实实二维跑优化,把Pareto前沿趋势找出来,再拿前沿上的少数候选点用三维模型精算验证,这是性价比最高的策略。
第二板斧是结构对称。如果模型本身有一半甚至四分之一对称性,就在Maxwell里加对称边界,只建对称部分,单元数直接砍半,求解速度接近翻倍。对称边界的磁通平行/垂直条件要在建模时根据磁场分布想清楚,这里不能偷懒,边界条件设错结果全废。
第三板斧是放宽收敛标准。自适应网格剖分的能量误差默认往往设得比较紧,比如1%。优化过程中,我们要对比的是不同参数组合的相对优劣,不是绝对电磁性能,完全可以放宽到2%到3%,单次求解时间能再降几秒到几十秒。最后一两代再回到严格收敛标准精算Pareto点,做最终决策。另外,Max自适应Pass数也别设太大,我一般设8到10,足够这个量级的模型收敛。
还有一个更激进的两阶段思路:先用粗网格跑20代,锁定几个有潜力的区域;然后重置变量边界到这些区域,细网格再跑剩下的30代。这个思路相当于"先广撒网,再重点捞鱼",总时间能压缩到原来的六成甚至一半,代价是可能错过极个别的孤立最优区域,工程上一般可以接受。
6.3 一周调试踩过的坑
这套流程我调试了一周,踩的坑写出来能绕桌子一圈。挑几个最典型的:
变量命名别带花活。一开始我把变量取名PM_Thickness、AirGap_Length,Maxwell脚本解析时在某些版本里偶发异常。后来全部换成x1到x5,一次通过。取名字一时爽,脚本解析火葬场,血的教训。
单位字符串拼接要小心。str(val) + "mm"最简单可靠,不要用f"{val} mm"带空格,更不要写"5*mm"这种带乘号的,Maxwell表达式解析器对格式变化很敏感,想少踩坑就统一最朴素写法。
CSV列顺序会变。不同Maxwell版本导出场报告时,CSV的列顺序可能不一样,有的第一列是位置,有的第一列是数值。我在MATLAB侧读CSV时先用readmatrix再固定按列取值,结果换了一台机器就翻车。后来在脚本里给每一列写表头,MATLAB侧按表头名取列,问题才根治。
失败个体不能中断循环。某个个体网格剖分不收敛、参数组合产生几何重叠,求解器偶尔会报错,这时如果脚本直接中断,整代结果全废。我在Maxwell脚本的循环体里加了try/except,失败个体直接跳过,对应目标值置为一个巨大惩罚数,比如[10, 1e6, 10]。它在非支配排序里必然被淘汰,流程却能继续往下走。这个妥协非常实用。
许可证竞争问题。有人图快,在MATLAB里用parfor并行跑多个Maxwell进程,结果许可证不够,进程互相抢占,卡死一整个下午。Electronics Desktop的许可证机制对多实例支持不算友好,老老实实串行跑才是正道。真想并行,可以准备多套许可证并行的多机方案,但那是另一个量级的投入了。
结果文件污染。上一代的结果CSV没删除,新的一代跑完后部分文件没更新,MATLAB读到了旧数据,Pareto前沿里混进几个诡异点。后来严格按代标签命名文件,每代开头清空专属目录,再也不出这种灵异现场。
6.4 Pareto前沿的后处理与选点决策
优化跑完,gamultiobj会返回一组Pareto前沿解,三维散点是最直观的画法:
figure; scatter3(fval(:,1), fval(:,2), fval(:,3), 40, fval(:,3), 'filled'); xlabel('-B_peak (T)'); ylabel('Mass (kg)'); zlabel('B波动率');注意第一列是负磁密,画图时用-fval(:,1)才是真正的磁密值,这一点不处理,图上看到的就是一条倒挂的曲线。
前沿上的点全部是非支配的,选哪个要看业务约束。我的惯例是先把三维前沿投影到最有意义的两个目标上,比如磁密和重量的二维平面,然后找这条曲线上的"拐点",也就是膝盖点。膝盖点的特征是:你再往磁密方向多走一点,重量会剧烈上升;你再往轻量化方向走一点,磁密会剧烈下降。这个点往往是工程上最划算的折中,我会优先拿着这个点和客户讨论,而不是把边缘的极端解当推荐方案。
如果业务里已有硬约束,比如"总质量必须小于5kg",就先筛选出满足约束的Pareto点,在剩余点里再挑磁密最高、波动率最小的。筛选逻辑很简单,但前提是目标值统计口径要一致,尤其f2的质量计算,最好和实际称重做过一次校准,否则你挑的点在仿真里完美,落地时被成本部门骂惨。
整套流程跑通之后,我最深的体会是:数据实时交互不是目标,而是手段。让优化循环真正转起来的,不是花哨的COM直连或实时推送,而是一套稳定的批处理加文件同步机制——看着笨,但它能让算法和求解器各司其职,几千次迭代不出岔子。瓶颈也从"能不能交互"变成了"单次仿真能不能更快"。我后面如果把这个案例升级到三维瞬态,单次仿真时间会成倍拉长,肯定得上代理模型,用Kriging或神经网络先近似替代求解器,把优化器喂饱了再回头精算候选点。多目标寻优的框架倒是可以原封不动留着,换的只是"做题的人"而已。