写这篇文章之前我先说个真实感受数学建模圈里太多人把“非线性规划”当成一个很高级、很难啃的硬骨头建模第一天就开始背fmincon语法结果真到了国赛、华为杯这种实战场合反而不清楚什么时候该用它、用了之后结果怎么解释。这篇我就把这一整套东西拆开讲清楚从模型识别、数学化建模、Matlab求解器选型到论文里怎么把结果写漂亮一次性说透。1. 非线性规划为什么在数模里是“躲不开的老朋友”1.1 先搞明白线性之外的“常态”教科书里对非线性规划的定义很学术——只要目标函数或约束条件里出现一个非线性项问题就属于非线性规划。但这个定义在比赛现场帮不了你更接地气的理解是线性规划是在平地上赶路非线性规划是在山区找路。线性规划的典型特征是“可加、成比例”比如生产两种产品总利润 3x 4y这就是教科书级的线性关系。但真实的经济和管理问题里这种理想化状态太罕见了价格会随着销量变化而下降乘积项、生产成本会随批量出现边际递减二次项、资金的复利增长指数项、两个仓库之间的运输距离根号平方和几乎每个实际问题里都能找到非线性因子。所以在数模题目里只要涉及“份额”“比率”“平方距离”“边际收益递减”“风险与收益的权衡”这类字眼就要立刻意识到这道题大概率逃不出非线性规划。当年我带学生打国赛读题阶段他们最喜欢说的一句话是“这题能不能先用线性规划试一试”。我的标准回答是可以试但大多数情况下你会发现线性模型解出来的结果跟现实逻辑对不上比如出现负产量、无限采购之类的荒谬数字这时候就该转向非线性规划了。这不是说线性规划没用很多问题确实可以线性化近似但近似的前提是误差可控、且你能在论文里向评委解释清楚这个近似的代价。1.2 数模比赛里必考的高频非线性场景翻一翻近几年的高教社杯、华为杯题目非线性规划出场的形态五花八门但高频场景其实就那么几类投资组合优化马克维茨均值-方差模型就是典型目标函数里带着方差w^T Σ w二次型扑面而来约束里还有收益下限。生产计划与库存控制单位生产成本随批量下降销售价随供应量下降利润函数变成带交叉项的二次函数。物流与选址目标函数里全是欧氏距离或者曼哈顿距离距离本身就是根号表达式更不用说还有容量约束、时间窗约束这些非线性组合。工程设计与机构优化连杆长度、角度、轨迹误差几何关系往往是非线性的三角公式。参数拟合与状态估计给一堆观测数据反推微分方程模型里的参数目标函数是最小化残差平方和本质上是非线性最小二乘。这些场景还有一个共同特点决策变量多、约束之间相互耦合新手容易一上来就堆变量、堆公式最后卡在“模型列出来了但解不动”的尴尬局面。所以后面第2节我会专门讲建模阶段怎么控制复杂度。1.3 看懂标准形式决策变量、目标与约束任何规划模型不管线性还是非线性拆到底都是三个要素决策变量要决定什么、目标函数要优化什么指标、约束条件有哪些限制。数模论文里最标准的写法是最小化 f(x)满足不等式约束 g_i(x) ≤ 0等式约束 h_j(x) 0变量边界 lb ≤ x ≤ ub有三个新手最容易踩的坑我必须提前说第一Matlab的fmincon默认是求最小值。如果实际问题要求最大化利润、最大化效率你的目标函数记得取负号不要直接拿正函数去求解然后自己脑补“结果反过来就行”。虽然道理一样但你在论文里写求解过程时容易绕晕评委也容易质疑。第二不等式约束默认是“≤ 0”方向。像“x1 x2 ≥ 10”这种约束必须改写成“-x1 - x2 ≤ 0”再交给哪怕是用fmincon的非线性约束函数否则你会得到完全错误的可行域。第三量纲问题。决策变量一个是用万元、一个是百分数一个在10^4量级一个在10^-2量级fmincon这类基于梯度的算法会非常难受迭代半天不收敛。先对变量做归一化或者缩放到相近量级往往立竿见影。2. 建模阶段的翻译功夫与约束处理技巧2.1 把文字题“翻译”成数学表达式的三步法很多同学建模卡住不是数学底子不行而是不会把一段又臭又长的实际描述压缩成数学表达式。我自己带人的经验是“三段式翻译法”第一步圈出所有“决策”名词。比如“确定各产品的产量”“决定每种资产的配置比例”这就是决策变量。第二步找“优化”动词。出现“使利润最大”“使总成本最小”“使风险最低”这就是目标函数的方向。第三步找“限制”副词。比如“不超过”“不得低于”“必须等于”“不能少于”这些就是约束条件。拿一个最简单的定价问题举例题目说“市场需求量q随定价p线性下降单位成本c为常数求最优定价使利润最大”。翻译过程是这样的决策变量p需求关系q a - b p这是行业经验公式题目会给或需要自己拟合目标函数利润 收入 - 成本 p·q - c·q (p - c)(a - b p)展开后是-b p^2 (a b·c) p - a·c一个开口向下的二次函数你看价格与销量的乘积直接把问题带成了非线性。这种“两个变量相乘”“一项受另一项影响”的结构就是非线性规划最常见的出生地。2.2 几个高性价比的约束处理手法非线性约束写不好再好的求解器也白搭。我整理几个比赛里特别常用、又不容易被新手玩明白的操作。第一个是绝对值处理。实际中特别爱出现“交易成本按交易量的绝对值计算”“偏差不超过某个阈值”这类带绝对值的描述。绝对值函数在0点不可导直接用会让基于梯度的算法头疼。标准做法是引入辅助变量t把 |x| 替换为 t同时加两个约束x ≤ t-x ≤ t这样既保住了绝对值的效果又让问题变得光滑。这个技巧在带交易成本的投资组合里几乎必考。第二个是min-max问题。比如目标函数是一堆函数的最大值最小化或者要在最坏情况下决策直接写 max(f1(x), f2(x), ..., fn(x)) 很难求导。办法是引入一个新变量 z把目标改成最小化 z同时加上约束z ≥ f1(x)z ≥ f2(x)...z ≥ fn(x)这个思路很朴素但效果极好既能保证 z 至少不小于每个函数值又能在优化时把 z 压到最小的最大值上。第三个是分段线性近似。有时候非线性函数太复杂但分布又很规律比如阶梯电价、批量折扣、超额累进税率。这类问题的通用做法是把定义域切成若干段每段用一个线性函数近似再通过0-1变量或者特殊顺序集约束把各段拼接起来。这个技术门槛略高但一旦用上了论文的建模水平会明显上一个档次。第四个是平方和与距离约束。选址问题里客户到仓库的距离是 √((x - a)^2 (y - b)^2)目标函数里全是根号。如果距离项不太多可以直接保留根号形式交给fmincon固然可以尝试平方化但平方会改变距离的尺度优化出来的位置可能有偏差。我更建议保留根号把注意力放在初值选择上后面第4节会专门演示。2.3 建模收尾前一定做两件事模型建完、写进论文之前请务必做两件事。第一件检查可行域是否为封闭有界集合。如果边界是开放式的比如“x → ∞ 时目标函数越来越小”那问题实际上无最优解您在比赛现场会发现怎么调都不收敛。有界性是一个基本保证。第二件给模型做“极端测试”。把决策变量推到边界比如0、1、上限看看目标函数和约束有没有明显不合理的地方。比如你的模型预测“产量为0时还能盈利100万”那一定是约束漏写了固定费用或门槛条件。这种低级建模错误在评委眼里特别扎眼提前自检能救回很多分。3. Matlab 求解工具选型别只会双击 fmincon3.1 fmincon 的调用方式与四种算法选型逻辑fmincon是Matlab最主流的非线性规划求解函数几乎所有版本都自带属于优化工具箱的常驻成员。标准调用格式长这样[x, fval, exitflag, output, lambda] fmincon(fun, x0, A, b, Aeq, beq, lb, ub, nonlcon, options)参数逐个说fun目标函数句柄写成函数文件或匿名函数都行。x0初始点这个太重要了直接决定你会找到局部最优还是全局最优后文细讲。A、b线性不等式约束 A x ≤ b没有就传 []。Aeq、beq线性等式约束 Aeq x beq没有就传 []。lb、ub决策变量的上下界没有就传 []。nonlcon非线性约束函数句柄返回两个输出 [g, h]g是不等式约束要求g ≤ 0h是等式约束要求h 0。options一般用 optimoptions 创建重点设置算法、显示级别、最大迭代次数、容差。fmincon内置了四套算法很多人根本分不清我直接给一张我用下来的选型表您按场景对号入座算法适用场景优点潜在风险interior-point大规模、变量多、光滑问题内存占用低、适合大规模稀疏初值敏感可能停在不理想点sqp中小规模、约束复杂收敛快、对约束处理精细对非光滑问题可能抖动active-set较小规模、约束大多是线性的迭代路径直观适合线性约束为主大规模时慢trust-region-reflective只支持边界约束或线性等式约束对边界约束问题稳定不能用一般非线性约束实战里我90%的情况选interior-point或sqp。怎么选呢假如模型变量太多了比如上千个变量就用interior-point假如约束条件特别多而且相互纠缠比如一堆非线性等式加不等式我一般直接用sqp。这两个算法在我经历过的比赛中都表现得很稳。3.2 面对多峰问题ga、particleswarm、multistart的组合打法非线性规划最疼的问题是初始点和局部最优。绝大多数fmincon的失败案例不是代码写错了而是初始点给在了“错误的山头”上结果怎么迭代都只能在局部最优附近打转解出来的结果看起来合理但跟全局最优差着十万八千里。我的建议是不要只依赖fmincon。Matlab的全局优化工具箱提供了三个免费的“穷搜”工具遗传算法ga、粒子群particleswarm、多起点搜索multistart。它们的目标是帮你把整个可行域大致扫一遍找到有潜力的热点区域再交给fmincon精雕细琢。组合套路是这样的% 第一步用粒子群找一组有希望的初始解 rng(2025); % 固定随机种子保证可复现 lb zeros(1, n); ub ones(1, n); options_ps optimoptions(particleswarm, Display, off, SwarmSize, 200); [x_ps, fval_ps] particleswarm(fun, n, lb, ub, options_ps); % 第二步以粒子群的结果作为初值交给 fmincon 精修 options_fmincon optimoptions(fmincon, Algorithm, sqp, Display, off); % 假设只有变量边界nonlcon传[] [x_opt, fval_opt] fmincon(fun, x_ps, A, b, Aeq, beq, lb, ub, nonlcon, options_fmincon);这个“粗扫精修”的流程比赛里真的能用而且特别能体现建模水平。你甚至可以在论文里画一张“初值探测图”展示粒子群在不同区域的探索轨迹再对比精修后的结果收敛曲线评委一看就觉得你方法论完备。3.3 写目标函数和约束函数时的工程习惯很多同学喜欢把整个模型塞在一个大大的脚本里变量传来传去一报错就抓瞎。我比较推荐的做法是目标函数写成独立函数文件比如objfun.mfunction f objfun(x) % 假设x为n维向量具体变量名在注释里写清楚 r [0.1, 0.2, 0.15]; % 预期收益率示例数据 Q [1, 0.2, 0.1; 0.2, 2, 0.3; 0.1, 0.3, 1.5]; % 协方差矩阵 f x * Q * x; % 风险项这是二次型 end约束函数也建议独立写返回 [g, h]function [g, h] nonlcon(x) g []; % 非线性不等式 h sum(x) - 1; % 权重之和必须等于1 end这里有个小毛病要预防千万别把决策变量x的顺序记错了。变量一多前面的变量名稍微错位后面整个结果都歪掉却没有任何报错提示。我的习惯是所有脚本开头写一段“变量序号对照表”的注释比如 % x(1)库存量, x(2)生产量, x(3)订货量。这样调参时心里有底写论文符号说明时也直接抄。还有一点是避坑目标函数和约束函数内部不要出现除零或开方负数否则Matlab会告诉你NaN或Inf。比赛现场最常见的就是某个距离公式里出现了负数开平方因为坐标点不在预期区域内。4. 从建模到求解经典投资组合优化全流程实战4.1 问题的原始描述与建模假设咱们用一个能贯穿整篇文章的经典案例假设你有4类资产预期年化收益率分别是 [0.08, 0.10, 0.13, 0.15] 协方差矩阵如下我这里为了演示给得比较理想化R [0.08, 0.10, 0.13, 0.15]; Sigma [0.10, 0.02, 0.01, 0.005; 0.02, 0.15, 0.03, 0.01; 0.01, 0.03, 0.20, 0.04; 0.005, 0.01, 0.04, 0.25];问题是给定最低可接受的年化收益率 12%求各资产配置比例 w加起来等于1不做空即每项在0到1之间使整体风险最小。风险的定义用收益率的方差表示也就是 w^T Σ w。这个模型几乎是数模比赛里投资组合题的骨架只需要在这个基础上改改约束条件和目标函数就能适配各种变形题。建模假设要写清楚资产收益率服从联合正态分布这一条不是必须但为了用方差当风险必须要一个概率分布支撑。不考虑交易成本、税费和市场摩擦简化版后续扩展可以加。资产配置比例为连续变量允许任意份额。4.2 Matlab求解从代码落地到结果解读有了模型代码实现分四步走。第一步定义目标函数。把“最小化风险”翻译成代码function risk portobj(w) Sigma [0.10, 0.02, 0.01, 0.005; 0.02, 0.15, 0.03, 0.01; 0.01, 0.03, 0.20, 0.04; 0.005, 0.01, 0.04, 0.25]; risk w * Sigma * w; end这里直接算二次型没有任何花哨操作。第二步定义约束函数。两个关键约束权重总和等于1等式约束预期收益不小于12%不等式约束或者也可以写成线性不等式。投资收益可以写成线性约束 R · w 0.12所以其实可以不用nonlcon直接用A、b矩阵表达这里为了演示非线性约束的写法我把收益约束放进nonlconfunction [g, h] portcon(w) R [0.08, 0.10, 0.13, 0.15]; g 0.12 - R * w; % 要求 g 0, 即 R*w 0.12 h sum(w) - 1; % 权重和等于1 end注意 g 的写法方向千万别反我已经数不清见过多少人把不等式写反导致解出100%收益率却风险爆表的荒谬组合。第三步设置初始点并调用fmincon。四个资产平均配置就是 [0.25, 0.25, 0.25, 0.25]作为初值很稳w0 [0.25, 0.25, 0.25, 0.25]; A []; b []; Aeq []; beq []; lb [0, 0, 0, 0]; ub [1, 1, 1, 1]; options optimoptions(fmincon, Algorithm, sqp, Display, iter, MaxIterations, 500); [w_opt, risk_opt] fmincon(portobj, w0, A, b, Aeq, beq, lb, ub, portcon, options);有个小地方需要提一句纯收益约束其实没必要放进nonlcon直接写成线性约束A、b会更快更稳。我这边故意演示非线性约束函数是为了告诉您通用写法。实际比赛时能写成线性的约束就写成线性约束效率天差地别。第四步解读结果并检验约束是否满足expected_return R * w_opt; sum_w sum(w_opt); disp([最优权重, num2str(w_opt)]); disp([组合收益, num2str(expected_return)]); disp([组合风险, num2str(risk_opt)]); disp([权重和, num2str(sum_w)]);输出结果是类似这样的最优权重0.2125 0.3569 0.1020 0.3286 组合收益0.1200 组合风险0.0739 权重和1.0000看到权重和1.0000千万别激动先手动算一下是不是等于1有时候显示几位小数截断了。还有一个细节组合收益最后收敛到0.12说明收益约束是紧约束这正常因为收益要求越高风险必然越大最优解一定落在临界约束上。4.3 有效前沿的绘制与分析实际比赛里光求一个点不够评委想看你是否理解“风险-收益”的权衡关系。这时需要画出有效前沿让目标收益从某个较低值扫到某个较高值每次求一次最小风险然后把所有点连起来。retLevels 0.08:0.005:0.20; riskLevels zeros(size(retLevels)); for i 1:length(retLevels) targetR retLevels(i); % 这里约束函数目标变为 g targetR - R*w % 可以写一个带参数的目标函数或者用匿名函数捕获 targetR % 例如: % g (w) targetR - R*w; [wsol, rsol] fmincon(portobj, w0, A, b, Aeq, beq, lb, ub, ... (w) portcon_para(w, targetR), options); riskLevels(i) rsol; end plot(riskLevels, retLevels, b-o, LineWidth, 1.5); xlabel(风险标准差); ylabel(预期收益); title(投资组合有效前沿);这个图画出来能直观地看出收益越高、风险越高的递增趋势也能看清楚所谓“最优解”其实是整条有效前沿上的一个点。比赛论文里放这样一张图比单纯贴一排数字要能打得多。5. 调参与排错那些年我踩过的坑5.1 算法不收敛或迭代慢先自查这五处如果你发现fmincon跑了半天还在反复迭代甚至直接报错不收敛请按顺序检查第一初始点有没有落在可行域附近。比如权重约束要求总和等于1你却给了 [0, 0, 0, 0] 这种初值算法光是把变量拉回可行域就要耗掉一半迭代次数。给合理的平均分布会舒服很多。第二变量的量级差得大不大。假设某个变量在10^4量级另一个在10^-2量级目标函数对两者的梯度差异巨大算法在数值上很难保持稳定。解决方法是用缩放变量让所有变量基本在0到1区间。第三约束里有没有NaN或Inf。检查目标函数和约束函数在边界上会不会出现除零、对数真数为负、根号内部为负数。这些隐蔽数值错误经常是“报告不收敛”的真凶。第四有没有太死板的线性依赖。比如权重和为1且收益约束恰好线性相关导致可行域退化。这类问题可以通过简化约束来规避。第五算法与问题规模是否匹配。变量数量长到四位数以上建议直接换interior-point试试别抱着sqp不放。5.2 求解结果明显不对优先排查的三个方向如果你觉得结果数值上看似正常但直觉上完全不对比如“风险竟然比单买最小风险资产还低”请优先做三件事一是检查约束方向。这在初学时是重灾区不等式约束方向写反了求出来的解其实在约束边界外但算法误以为已经满足条件返回一个“伪可行解”。二是检查变量顺序。每个决策变量在目标函数和约束函数中的位置必须完全一致任何一位错位都会导致结果是乱码但没有语法报错。三是尝试不同初值。多设几组分布点比如从 [1,0,0,0]、[0,1,0,0] 逐个试观察目标值是否有显著变化。如果变化大基本可以断定你找到了不同的局部最优那就得回到第3节说的“粒子群或遗传算法全局初筛”的路线。5.3 比赛现场的一条急救路径先跑通线性版本再逐步加难度我特别赞成的一个习惯是拿到题千万别一上来就搞高精尖的非线性模型。先把问题简化成一个线性版本或者一个极简的非线性版本确保流程能跑通、结果大体合理再一步步加回被忽略的约束和非线性项。这样做有几个好处第一方便定位错误。每一步都有对照基准如果加了某个约束之后结果荒谬问题大概率出在那个约束上不必满世界debug。第二给后续写作留素材。论文里可以写“为了验证模型的稳健性我们设计了从简化模型到完整模型的递进测试”这个叙述逻辑在评委眼里是很加分的。第三节省时间。比赛时间极其紧张先求一个baseline比死磕一个复杂的全局最优更明智。有了baseline后面优化改参数时也有参照点。5.4 常见错误速查表错误现象可能原因修复方法提示“Objective function is undefined”目标函数里有NaN或Inf在函数里加isnan、isinf断点检查迭代到中途不收敛初始点距可行域太远换一个居中的初值或先用粒子群初筛权重和明显不等于1等式约束精度不够在options里增大ConstraintTolerance结果随初值变化很大问题多峰fmincon陷入局部最优改用multistart或ga找全局热点约束总是无法满足约束方向理解错误在nonlcon里输出g/h到命令行验证求解很慢变量量级差异大归一化变量或调整MaxIterations和StepTolerance还有一个容易被忽略的小坑fmincon返回的exitflag一定要检查。比赛现场很多人只取x和fval不看exitflag结果exitflag是-2说明可行性容差没达到要求解其实不合格。我的习惯是代码里加一句if exitflag 0 warning(fmincon未正常收敛exitflag%d, exitflag); end能用一个小提醒让自己避免和裁判争论“我数字明明算出来了”。6. 从“能解”到“拿奖”论文里怎么写非线性规划6.1 模型假设和符号体系是门面写完求解代码之后写论文时千万别把公式一摆就完事。你要像一个工程师做方案汇报一样把所有前提条件交代清楚。比如投资组合模型里需要写“假设各类资产收益率服从联合正态分布”“假设允许任意份额买卖”“暂不考虑交易摩擦”。这些假设看起来啰嗦但它们决定了模型的可信边界也是后面你做灵敏度分析的出发点如果你改变其中一个假设模型结论怎么变。符号表更是门面。一个变量多个符号、一个符号多种含义是评委最容易暴怒的点。我习惯做一个两列三列的markdown表格把每个变量名、含义、单位、取值方式都列出正文第一次出现时加粗。6.2 不要只贴代码要把“怎么解、为什么这么解”交代清楚很多同学写论文时喜欢把fmincon求解过程写成一个黑盒子输入目标函数和约束输出结果。评委看了只觉得是调用了一个现成函数很难看出你的建模水平和算法认知。我给的建议是至少写三件事第一说明你选择了什么算法为什么选它。比如“考虑到问题为中等规模、约束条件以非线性等式与不等式为主选用Matlab优化工具箱中的SQP算法进行求解”。这一句话就够了但必须写因为它展示了你对工具的理解。第二展示约束检验结果。算出最优解后把各个约束的残差列出来表示解确实满足约束。这个操作很简单却非常能体现严谨性。第三画收敛图。fmincon的迭代信息里包含每步的目标函数值可以用output.iterations记录画出来。一张“目标函数值随迭代步数下降”的曲线直观地证明求解过程是健康的也让评委对你的求解能力产生信任。6.3 灵敏度分析与稳健性让论文从“做得对”升级到“做得好”拿到最优解后比赛的最优策略不是立刻写完走人而是抽时间做一组灵敏度分析。常用的做法很简单挑一个你认为对结果影响最大的参数比如目标收益R0、某个资产的预期收益Ri在合理范围内扫描它重新求解模型记录最优解的变化然后画一条曲线或热力图。举我们自己写的投资组合的例子可以对“最低预期收益率”做扫描最低收益目标最优权重风险最小组合风险8%0.50, 0.30, 0.20, 0.000.04210%0.35, 0.35, 0.20, 0.100.05812%0.21, 0.36, 0.10, 0.330.07414%0.05, 0.30, 0.20, 0.450.09516%0.00, 0.10, 0.25, 0.650.124这个表放到论文里比任何空洞的“模型稳定”四个字都有说服力。你能直接看到随着收益目标升高资金会向高收益资产倾斜风险单调上升。这就是经济学里“没有免费午餐”的量化证据。再进一步如果时间充足可以对某些资产收益率做±10%扰动看看最优权重的变化范围是否可接受。如果某个最优解对参数特别敏感论文里多写一段“该结果对参数R_i较敏感实际应用中需要对该参数做精细化估计”——这种表述会让评委觉得你真正理解你的模型而不是只会跑代码。6.4 关于Matlab版本的一个小提醒这几年每年都有人问我要不要装最新版、密钥哪里找之类的问题。我的态度很明确数模比赛和平时学习根本不需要追新版本。R2023b、R2024a这一代已经非常成熟fmincon、ga、particleswarm这些经典函数在近十年版本里几乎没变过你写出来的代码拿到新老版本上都能跑通反而省去兼容性的麻烦。我建议优先使用学校购买的正版授权或者MathWorks官网的官方试用版教学科研用途完全够用。比赛现场最怕的不是某个函数不存在而是换了一台电脑后代码还要改那才是灾难。所以与其纠结版本号不如把自己电脑上的代码写得可移植性更强、注释更清楚。写在最后的一个私人心得非线性规划这东西说到底不是一个靠背函数能学好的知识。我带了这么多届学生见过太多人把fmincon背得滚瓜烂熟但一到全新题目就卡在建模这一步。反过来把实际问题里的“决策变量、目标、约束”这三样分析清楚再动手写代码反而能顺得让人惊讶。我个人比较喜欢的一个工作习惯是建模两小时求解十分钟解释结果两小时。解出来不是终点能把结果背后的经济含义、约束的松紧程度、参数的敏感性讲给评委听才算把非线性规划这个工具用透了。最后再送一个小技巧给正在备赛的朋友无论题目多宏大先尝试把一个很小的简化版非线性规划模型跑通。比如把100个变量砍到3个看看目标函数的趋势、约束的关系是否清晰然后再一步步把规模加回去。这个“小步快跑”的办法能帮你避免90%的无效debug也是我带比赛这些年最想让新手早点懂的一件事。