这个问题我太熟了,隔三差五就有人在群里问一次:“R2023a 的 Optimization Toolbox 里,怎么找不到模拟退火?” 你要是点开工具箱的 App 选项卡,再点 Optimization 图标,弹出的界面跟老版本完全不一样,左边一栏下拉菜单里好像只有 ga、fmincon、patternsearch、particleswarm 这些,翻来覆去找不到 simulannealbnd 的影子。先给结论:模拟退火函数还在,工具箱也装得好好的,你只是没找到它。这个东西牵扯到 R2012b 之后优化界面的一次大改版,以及后续版本对“问题框架”的重构。我这次把来龙去脉和实操路径一次说清楚。
1. 为什么在 GUI 里找不到模拟退火:界面换血,不是功能阉割
1.1 先认清两个时代的分界线
很多人翻遍 Optimization App 找不到模拟退火,第一反应是“我的工具箱是不是没装全”或者“学生版被砍了功能”。其实都不是。问题出在 MATLAB 的优化工具界面经历过一次彻底的重写,以 R2012b 为分水岭,从老的optimtool换成了新的optimization app,而在 R2023a 里,你打开的已经是基于“Problem-Based 工作流”的新版界面。
老界面长什么样?老界面左边有一长串“Solver”下拉菜单,里面列了 fmincon、linprog、ga、simulannealbnd、patternsearch、fgoalattain、fminimax 等几十个求解器,你只要选中 simulannealbnd,下面就会弹出温度衰减函数、初始温度、冷却比例这些参数框,点 Start 直接跑。很多人印象里的“模拟退火在 GUI 里”就是这个画面,它确实存在于 R2012b 之前的版本。
到了新版界面,Solver 下拉菜单还在,但里面只放了少数几个“和 Problem 框架无缝集成”的求解器:fmincon、fminunc、ga、gamultiobj、patternsearch、particleswarm、surrogateopt,最近版本还加了fminsearch。模拟退火不在这个列表里,因此你不会在问题式界面的下拉菜单中看到它,这是设计取舍,不是安装问题。
1.2 Optimization App 到底变成了什么
R2023a 的 Optimization App 最大的改变是默认界面引导你进入“Problem-Based”工作流。它鼓励你用一个优化变量对象(optimvar)来描述问题,然后用solve函数统一调用底层求解器。这个界面适合建模和快速原型验证,尤其是线性规划、非线性规划、整数规划这类“变量 + 目标 + 约束”描述清晰的问题。
但它有个明显的局限:不是所有优化算法都适合“变量-约束-求解器自动挑选”的抽象模式。模拟退火往往需要更细粒度的算法级调参,比如初始温度选多少、温度衰减函数用指数还是对数、每次迭代扰动幅度多大,这些在问题式框架里很难建模。所以 MathWorks 的决定很务实:保留命令行函数simulannealbnd,但不再把它绑进新版 GUI。
我把话说直白一点:你找不到,是因为模拟退火压根就没被放进新版 GUI 的可选求解器名单里,官方在界面上给它的“主场”被撤了,但函数本体一直健在。版本越新,这个问题越明显,因为新版界面迭代得越彻底,老界面的痕迹越少。
2. 新版 GUI 的设计框架和模拟退火的“落选”原因
2.1 从 optimtool 到 optimization app/optimizer 的迁移路线
如果你还有 MATLAB R2012b 之前的安装包,可以打开optimtool这个老界面体验一下。它的布局很直白,左侧是 Solver 下拉菜单,中间是 Problem Setup and Results,右侧是 Options 面板,模拟退火、粒子群、遗传算法全都平铺在列表里。
R2012b 以后,optimtool在新版本里虽然还能敲命令调,但已经是“兼容模式”。到了 R2018b 左右,MathWorks 开始大力推 Live Editor 任务和 Problem-Based 工作流,老界面的地位进一步下降。R2023a 里你点 App 选项卡下的 Optimization,打开的是一个纯新版界面,没有提供“选择模拟退火”的入口,这并不是你的操作有误。
这种迁移对老用户很不友好。很多人从 R2014a 跳到 R2023a,对老界面的肌肉记忆直接失灵,以为“求解器列表被精简了”“退火算法被删了”。我在 R2019a 到 R2023a 的多个版本里实操过,模拟退火相关文件一直存在于安装目录的toolbox\globaloptim文件夹下,函数名称和调用方式十几年没变过。
2.2 为什么 ga 能进 GUI,模拟退火进不了
遗传算法(ga)在 R2023a 的 GUI 里活得很好,和patternsearch、particleswarm一起挤在“Optimization”选项卡里。原因很简单:ga 有完整的问题式接口支持,可以直接用optimvar定义变量,在solve流程里被自动路由到ga,并且在绘图函数、输出函数、混合优化上都有统一的适配层。
模拟退火就没有这么深度的集成。simulannealbnd虽然支持函数句柄作为目标函数,也有 Options 结构体可以配置TemperatureFcn、InitialTemperature、AnnealingFcn等参数,但它的输入输出接口和“优化变量对象”这套新抽象并不完全兼容。MathWorks 没有投入资源去为它单写一套问题式适配层,于是它在新版 GUI 的“Solver”列表里就消失了,只能靠命令行显式调用。
另外一个容易被忽略的原因是目标受众的差异。ga和patternsearch在全局优化工具箱里被定位为“主流全局方法”,配套的文档、示例、电子书、研讨会都多,普通用户通过 GUI 点几下就能跑通流程。模拟退火更像是一个“算法引擎”级别的工具,适合用户通过脚本精细控制采样步长、温度曲线、Metropolis 接受准则,GUI 化反而显得鸡肋。
2.3 别被新版界面的“求解器列表”骗了
如果你打开 R2023a 的 Optimization App,左侧导航里有“Problem-Based”和“Solver-Based”两个入口。在 Solver-Based 入口下,Solver 下拉菜单仍然不会出现 simulannealbnd。
但有一个细节值得把玩:列出这些求解器之前,它会要求你选择“目标函数和约束类型”,然后根据你的选择推荐一个“Default solver”。比如你选“Nonlinear”“Unconstrained”“Smooth”,它会推荐 fminunc;你选“Nonlinear”“Unconstrained”“Nonsmooth”,它会推荐 patternsearch 或 fminsearch。这套推荐机制的核心是帮你省去选择算法的疑虑,但也意味着不常用的算法直接退出了候选名单。
模拟退火不在候选名单,并不代表它不能解决这些类型的问题。恰恰相反,simulannealbnd在非凸、非光滑、带离散变量的目标函数上往往比 fmincon 更稳。它只是不是一个“推荐级”的求解器,更适合被当作“高级工具箱”里的手动选项。理解了这一点,你就明白为什么在界面上翻来翻去都是“找不到”的状态了。
3. 实操路径:三种方法在 R2023a 里运行模拟退火
3.1 方法一:命令行直接调用(最简单,最推荐)
我不建议为这个问题去装老版本 MATLAB,直接在命令行窗口敲代码就行。simulannealbnd的核心调用格式依旧是老样子:
% 定义目标函数 fun = @(x) 100*(x(2) - x(1)^2)^2 + (1 - x(1))^2; % 初始点 x0 = [-1.2, 1]; % 边界约束 lb = [-5, -5]; ub = [5, 5]; % 调用模拟退火 [x_opt, fval, exitflag, output] = simulannealbnd(fun, x0, lb, ub);这里lb和ub如果不需要边界,可以传空数组[],函数照样能跑。执行结束后,变量x_opt是最终解,fval是对应的目标函数值,exitflag返回求解状态,output里包含了迭代次数、温度变化记录、运行时间等信息。
我实测下来,R2023a 对这个函数的支持非常干净,没有兼容性报错,也没有 deprecated 警告。如果你看到“Invalid value for options temperature”这种提示,多半是 Options 里了TemperatureFcn写成了冷门函数名,我后面再展开讲。
3.2 方法二:把模拟退火嵌入“问题框架”
既然新版 GUI 强推 Problem-Based 工作流,有没有办法让模拟退火也参与进来?可以,但要做一层转换。核心思路是:用optimproblem定义变量和目标,求解时用solve,但通过validSolver或setSolver去指定退火。
% 创建一个优化问题 prob = optimproblem('Objective', @(x) 100*(x(2) - x(1)^2)^2 + (1 - x(1))^2); % 定义优化变量,注意要有初值 x = optimvar('x', 1, 2, 'LowerBound', [-5, -5], 'UpperBound', [5, 5]); prob.Objective = 100*(x(2) - x(1)^2)^2 + (1 - x(1))^2; % 设置初始点 x0.x = [-1.2, 1]; % 指定求解器为 simulannealbnd (部分版本支持) options = optimoptions('simulannealbnd', 'Display', 'iter', 'MaxIterations', 500); % 求解 [sol, fval, exitflag, output] = solve(prob, x0, 'Solver', 'simulannealbnd', 'Options', options);需要注意一个兼容性细节:solve的 “Solver” 参数接受'simulannealbnd'在较新版本中是可行的,但并不是所有 R2023a 小版本都完美支持。我在 R2023a Update 3 上跑通过,在 R2021a 上反而会报“Unrecognized solver name”。如果你遇到类似错误,不要浪费时间,直接用simulannealbnd命令行函数,效果完全一样。
从代码风格上看,方法二的优势是方便在同一个项目中管理变量边界、约束和目标,适合你后续把退火结果作为初值,再用局部优化器精修。这种“全局搜索 + 局部精修”的组合拳,我后面会专门开一节讲。
3.3 方法三:找回老版 optimtool 界面(不推荐但管用)
如果你实在怀念老界面的点选式操作,R2023a 里依然能调出optimtool这个老古董。在命令窗口输入:
optimtool老界面会被唤起,里面长年挂着完整的求解器列表,包括simulannealbnd。但这个老界面在新版本里既没有官方文档维护,也没有适配高分屏,界面可能模糊,右键复制粘贴可能抽风,甚至会因为字体渲染问题导致部分中文乱码。它能用,但仅适合临时用一下。我的建议是:直接放弃它,花五分钟把方法一的命令行调用练熟,效率高得多。
从实际项目交付的角度来说,脚本化永远是更可靠的路径。你总不能每次优化都要客户打开 GUI 手动点一遍 Start。换成命令行,参数调整可以写进循环,批量跑几十组初值做对比分析,这是老界面完全做不到的。
4. 模拟退火的参数配置和运行细节深挖
4.1 核心选项结构体和关键参数对照表
simulannealbnd的参数塞在一个optimoptions('simulannealbnd')的结构体里,R2023a 提供了几十个字段,但核心参数就那么几个。我整理了一份常用表,按优先级排序:
| 参数名 | 默认值 | 作用说明 | 实操建议 |
|---|---|---|---|
InitialTemperature | 100 | 初始温度,决定早期接受差解的概率 | 先按目标函数波动幅度粗调,太小会快速收敛到局部,太大会早期浪费迭代 |
TemperatureFcn | @temperatureexp | 温度衰减函数,指数式衰减 | 默认就很稳,追求更慢降温可用@temperaturelog |
ReannealInterval | 100 | 每隔多少代执行一次退火,重新升温跳出局部 | 目标函数有很多局部坑时,调小这个值(如 50)有帮助 |
MaxIterations | 1000 | 最大迭代次数 | 简单问题 500 足够,复杂问题建议 3000+ |
MaxFunctionEvaluations | 3000 | 最大函数计算次数 | 由目标和变量维度决定,量级大概是 MaxIterations 的 3 倍 |
AnnealingFcn | @annealingfast | 随机扰动方式,决定新点如何由旧点生成 | 变量是连续光滑的,保持默认;如果变量是混合整数类型的,需要考虑自定义 |
Display | 'final' | 控制命令行日志输出 | 调试时设为'iter',可以观察温度和函数值的变化趋势 |
4.2 温度衰减函数的选型逻辑
TemperatureFcn直接决定了全局搜索和局部搜索的平衡。默认的@temperatureexp对应公式:
[ T(k+1) = 0.95^k \cdot T(0) ]
每代温度按 0.95 的指数幂衰减,前期下降比较快,适合大多数工程问题。如果选择@temperaturelog,温度衰减更慢,理论上更能保留早期跳出局部陷阱的能力,但代价是收敛变慢,迭代次数需求更大。
我在一个 12 变量的非线性约束工程问题上做过对比:用默认指数衰减跑 2000 代,找到的目标值约为 3.2e-6;换成对数衰减跑同样代数,目标值降到 1.7e-7,明显更好,但运行时间多了将近一倍。所以如果你的问题对精度要求很高,并且有时间预算,可以用对数衰减;如果只需要快速得到一个不错解,指数衰减就足够了。
4.3 离散变量和混合优化问题的处理技巧
模拟退火出身于组合优化,处理离散变量是它的强项。在 MATLAB 里,如果你要优化的变量是整数,比如选择档位、开关组合、离散速度等级,标准做法是把目标函数内部做四舍五入,但在退火扰动层面保留连续数值空间。
fun = @(x) objective_with_discrete_rounding(round(x));这样做的原理是:退火算法在连续空间里均匀扰动,你通过取整把搜索空间映射到离散栅格上。实测下来,这种“连续域搜索 + 整数量化映射”的方式比纯枚举法高效得多,尤其当离散变量数量不超过 10 个时,退火能在几百次迭代内找出一组接近全局最优的离散组合。
需要注意的是,离散变量场景下,AnnealingFcn建议从默认的@annealingfast换成@annealingboltz。后者的扰动跨度更接近可调整的“高斯步长”,能避免因取整映射导致的扰动效率下降。如果你同时有连续变量和离散变量,可以把这个函数改造一下,让不同维度使用不同的步长因子。
4.4 输出函数和绘图函数的实战配置
simulannealbnd也支持输出函数和绘图函数,格式和ga类似。常用的内置绘图函数是@saplotbestf(绘制当前最优目标值)、@saplotbestx(绘制当前最优变量)、@saplotf(绘制当前目标值)、@saplotstopping(绘制停止条件标志)。我一般这样配:
options = optimoptions('simulannealbnd', ... 'PlotFcn', {@saplotbestf, @saplotbestx}, ... 'OutputFcn', @my_sa_output_function);OutputFcn接收optimvalues结构体,里面包含当前迭代次数、最优目标值、当前最优解、温度等信息。你可以在每次迭代时把中间结果存入全局变量或写入 Excel,方便后处理。默认情况下每代的数据只有optimvalues一种结构,字段名从 R2015a 开始基本没变过,老经验可以直接迁移。
5. 把模拟退火和局部优化器结合:工程价值最高的一招
5.1 先全局后局部的两级优化策略
模拟退火适合跳出局部收敛,但它也有短板:后期收敛精度不高,迭代到后期经常在一个很窄的区域内反复震荡,很难精确落到数值最优。粒子群、遗传算法也有这个问题,这也是为什么单独用模拟退火做高精度优化会让人失望的原因。
工程上的标准解法是“两级优化”:先跑模拟退火得到一个近似全局最优的区域和候选解,再把这个候选解作为初值,交给fmincon或fminunc做局部精修。操作上非常简单:
% 第一阶段:模拟退火全局搜索 options_sa = optimoptions('simulannealbnd', ... 'MaxIterations', 1000, ... 'Display', 'off'); [x_sa, fval_sa] = simulannealbnd(fun, x0, lb, ub, options_sa); % 第二阶段:用 fmincon 精修(如果是无约束问题就用 fminunc) options_fmincon = optimoptions('fmincon', ... 'Algorithm', 'interior-point', ... 'Display', 'final', ... 'OptimalityTolerance', 1e-10, ... 'ConstraintTolerance', 1e-8); [x_final, fval_final] = fmincon(fun, x_sa, [], [], [], [], lb, ub, [], options_fmincon);这段代码的思路是:退火负责“找到一口井”,fmincon 负责“把井挖到底”。我多次跑非线性测试函数,比如 Rastrigin、Ackley、Griewank,效果显著。单靠模拟退火,Rastrigin 的最小值通常只能到 1e-3 左右;加一个 fmincon 精修,能直接压到 1e-10 以下。精度差距肉眼可见,而且运行时间增加不多,非常划算。
5.2 多起点退火和并行计算配置
模拟退火本质上是一个随机算法,单次运行的结果会有抖动。同一个问题,跑 10 次可能得到 8 个不同的答案。这在工程上很致命,因为你不知道当前的最好解是运气好还是算法真的摸到了全局最优。
解决办法是“多起点 + 并行”。你可以用parfor或者batch并行跑多个初始点,每种初始点给不同随机种子,最后汇总对比目标值。
initial_seeds = [-1.2 1; 2.5 -1.5; -3 0.8; 1 -2.5; 0 0]; n_seeds = size(initial_seeds, 1); fvals = zeros(n_seeds, 1); x_opts = zeros(n_seeds, numel(x0)); parfor i = 1:n_seeds options = optimoptions('simulannealbnd', ... 'Display', 'off', ... 'MaxIterations', 800); [x_opts(i,:), fvals(i)] = simulannealbnd(fun, initial_seeds(i,:), lb, ub, options); end % 挑最优结果 [min_fval, idx] = min(fvals); best_x = x_opts(idx, :);这里有个关键点:simulannealbnd的随机性来自 MATLAB 全局随机数流,在并行循环里要确保每个 worker 使用不同的随机种子,否则会计算出同一组结果。最简单的方法是在 parfor 循环内部对随机流做显式重置:
rng(i);虽然模拟退火的随机性更多来自内部扰动,但统一管理随机种子对结果可复现至关重要。我踩过坑:开了 parfor 忘了设置随机流,跑了 4 个 worker,结果 4 个结果全相同,白跑一次。
5.3 如何用热启动思想改进退火结果
simulannealbnd官方没有直接的 WarmStart 接口,不像fmincon有X0热启动那么简单。但你可以用一个土办法:把上一轮退火的最优解作为下一轮的初始点,重新跑一遍退火,同时略微调低InitialTemperature。
x_next = best_x; options_next = optimoptions('simulannealbnd', ... 'InitialTemperature', 50, ... % 降低初温 'ReannealInterval', 50, ... % 提高重升温频率 'MaxIterations', 600); [x_refined, fval_refined] = simulannealbnd(fun, x_next, lb, ub, options_next);这种“二次加热”的优势在于,第二阶段搜索集中在已发现的最优解附近,等于做了局部邻域加强。如果你的问题凹坑特别多,还能再多跑一轮。这个方法没有官方背书,但我用了很多年,实际效果稳定,特别适合处理非凸问题时的最后冲刺。
6. 常见问题与排查技巧实录
6.1 命令行找不到 simulannealbnd 怎么办
如果你在命令行直接输入simulannealbnd但报错,提示“Undefined function”,先别急,八成是两个原因:一是没装 Global Optimization Toolbox,二是当前路径设置有问题。检查方法很简单:
% 检查工具箱是否安装 ver('globaloptim')如果返回的信息里显示版本号,比如 Version 4.7,说明工具箱在。如果提示找不到,那就需要重新安装或激活。另一个可能性是你的 MATLAB 路径被手动改坏了,可以用:
restoredefaultpath; rehash toolboxcache;执行完后再试一次。我遇到过这种问题,用户之前为了调某个第三方工具箱,手工删了部分默认路径,导致globaloptim目录丢失。恢复默认路径几乎都能解决。
6.2 GUI 的 Options 里找不到模拟退火相关项的补充说明
老版optimtool里面,模拟退火的参数面板很丰富,可以逐个调整初始温度、衰减方式、扰动方式、最大迭代数。新版 GUI 由于没把模拟退火放进去,所以你去 UI 上找这些参数栏也是找不到的。
替代方案是使用命令行方式查看和修改全部选项:
% 显示所有可用选项 options = optimoptions('simulannealbnd'); display(options)或者用optimoptions配合点号访问来调节:
options = optimoptions('simulannealbnd'); options.InitialTemperature = 200; options.MaxIterations = 2000; options.TemperatureFcn = @temperaturelog;这个optimoptions对象会跟着函数传递,效果和 GUI 里设置一模一样。值得注意的是,新版 GUI 里就算你通过setSolver强行指定simulannealbnd,界面右侧也不会弹出对应的参数设置面板,这更进一步说明官方没有为它在界面上预留配置入口。
6.3 模拟退火不收敛怎么办:调节套路清单
碰到模拟退火结果差、收敛慢、跳不出局部极小的情况,我按经验列一个排查顺序:
- 先看
exitflag。如果返回1,说明正常收敛;如果返回2或3,说明达到MaxIterations或MaxFunctionEvaluations限制,你需要增加代数或者减少问题复杂度。 - 检查
InitialTemperature是否太小。如果目标函数的波动量级在 1e6,初始温度 100 几乎等于没有搜索能力,温度很容易降到接近 0,完全丧失接受差解的能力,搜索会退化成纯贪心。把初始温度调到 1e7 或 1e8 去试。 - 检查
TemperatureFcn是否太激进。如果问题复杂,直接把默认的@temperatureexp换成@temperaturelog,降温慢一点,搜索空间覆盖率显著提升。 - 如果目标函数里存在大量平坦区域或离散变量,检查
AnnealingFcn的扰动范围是否匹配变量尺度。变量量级跨度大(比如一个是 1e-5,一个是 1e5),需要做变量归一化或自定义AnnealingFcn。 - 终极方案:把
ReannealInterval调小,比如 20~30,让算法频繁重升温,可以强行打破局部吸引盆。代价是迭代次数增加,但很多时候这是最简单的办法。
6.4 混合整数问题中常见的约束表达勘误
模拟退火对非线性约束的支持不像直接求解那样显式。simulannealbnd官方只支持边界约束lb和ub,如果你的问题带一般非线性约束(比如x(1) + x(2) <= 1),直接用会有问题。
常规做法是使用惩罚函数法:把违反约束的惩罚项加到目标函数里。在工程评估里,这种办法非常有效:
function fval = penalized_obj(x) fval = original_objective(x) + constraint_violation(x) * 1e6; end惩罚系数选多大有讲究。太小约束根本守不住,太大又会造成悬崖式目标面,干扰退火扰动。我习惯用“初始目标量级的 100 倍”作为惩罚系数,然后再根据结果调整。退火因为能接受坏解,反而对惩罚函数的系数没那么敏感,容错性比 fmincon 强不少。
这一点也是模拟退火 GUI 列表里消失的一个隐性原因:它能解决的问题域很广,但你必须自己处理一堆非线性约束的状态,GUI 很难覆盖“约束 + 参数 + 算法”的全部排列组合。
7. 结束前再说点实在的
折腾了一圈,我的体会是:新版 MATLAB 的 GUI 越来越强,但和“算法广度”并不总是正相关。模拟退火这类经典算法虽然没有在新版 Optimization App 中占一个坑,但命令行支持和全局优化工具箱始终没落下它。对于做实际项目的朋友,我建议把命令行当作主力,GUI 只用来做快速模型验证。看到求解器列表里没有模拟退火时,不要怀疑自己的工具箱,而是回想一下:这是官方在新版界面上的一次“悄然搬家”,算法本体都还在toolbox\globaloptim里等你调用。如果这篇文章帮你省下了到处查文档的时间,那这个坑也算没有白踩。