主从博弈与产消者竞价:配电网双层优化从建模到IEEE33节点复现

📅 发布时间:2026/10/20 8:28:29
主从博弈与产消者竞价:配电网双层优化从建模到IEEE33节点复现
1. 复现前的认知准备主从博弈与产消者竞价到底在解决什么问题1.1 为什么“产消者”成了配电系统的关键角色搞科研的都知道复现一篇EI论文有多耗人。尤其是涉及博弈论的电力市场类文章公式推导一大串代码却一句不给光靠文字描述想跑通结果没点耐心真扛不住。今天要聊的这个复现项目——基于主从博弈的新型城镇配电系统产消者竞价策略就是典型代表。我花了大概两个星期把全新的Matlab代码一点点搭起来最终在IEEE33节点算例上完整复现了论文的核心结果上层运营商定电价下层产消者做响应两层反复迭代最终达到一个稳定的均衡解。先说清楚一件事为什么现在“产消者”这个词这么火因为配电网的运行逻辑变了。过去配电网就是一个单向电力分配网络电从上级变电站往下流末端全是纯负荷。居民、商业楼宇、小工业用户只是“用电器”电价是什么就只能接受什么。但分布式光伏普及之后很多用户变成白天发电、晚上用电甚至屋顶光伏多了还能往外卖电。这类主体拥有发电和用电双重身份就是我们说的产消者。产消者一多配电网的动态就复杂了。你家里光伏多发一小时邻居的电压可能就抬高了你把储能放在谷时充电、峰时放电整个系统的负荷曲线会变平运营商的收益也会跟着变。这种双向互动里运营商和产消者之间并不是单纯的命令与服从关系而是各自有利益诉求的交易对手。要想把这种关系建模成可计算的问题主从博弈几乎是当前学术圈最常用的框架。这个复现项目最有意思的地方就是把博弈论和配电网潮流计算真真切切地拧在一起。不是那种简单套个概念就走的“伪博弈”而是电价变量、负荷决策、储能SOC、节点电压全部联动的那种完整模型。论文和代码都在IEEE33节点系统上跑这是配电网方向最经典的标准算例相当于深度学习里的MNIST几乎所有新模型都会先在这个测试系统上做对比验证。所以复现这套代码不仅仅是把某一篇论文的结果还原出来更是把“配电网决策类问题”从建模到求解的完整方法论掌握了一遍。1.2 主从博弈先出价的一方掌握主动权主从博弈又叫Stackelberg博弈核心思想可以浓缩成四个字先手优势。在配电网这个场景里谁先出策略谁就掌握了整盘棋的主动权。具体到这个项目配电系统运营商是领导者先公布一组分时电价产消者是跟随者看到电价之后根据自身的光伏出力、储能状态、用电需求计算出让自己最划算的购电量和售电量。运营商看到产消者的响应结果后发现自己这组价格不是最优的就调整电价再发布一轮。来回迭代直到双方都拿不出更好的策略问题就收敛了。这里有一个容易懵的点产消者明明也有“定价”空间为什么只能被动响应实际上在这个模型里产消者的“竞价”行为更多体现为报量不报价——通过改变自己的购售电量来对电价作出反馈。你想如果每个产消者都自己报价那就变成多领导者多跟随者的博弈模型的平衡解理论都要重新证明一遍。所以大多数论文走的是“运营商定价格、产消者定电量”这条路实现起来也更干净。我用一个生活化类比帮你理解菜市场里批发商先挂出西红柿的价格牌商贩看到价格后决定今天进多少货。批发商发现定价高了没人进货定价低了利润太少就来回调整价格牌直到双方成交量和利润都趋于一个稳定点。配电网里的主从博弈本质上就是这个过程只不过换成电价和电量中间再叠加上复杂的潮流约束。1.3 IEEE33节点在这个项目里的作用IEEE33节点系统是一个辐射状配电网测试算例基准电压12.66kV一共33个节点、32条支路根节点通过变压器连到上级电网。标准算例里每个节点都有固定的负荷数据全网总负荷约3715kW加2300kvar线路阻抗数据也是公开的。这套数据最早是上世纪提出的但因为结构典型、数据完整直到今天依然是配电网优化调度、重构、故障恢复、分布式电源规划等方向默认的基准测试系统。在这个复现项目里IEEE33节点承担了两个角色。第一它是整个求解模型的“物理容器”所有潮流方程、电压约束都要在具体节点上展开第二它是产消者接入的场景载体。原始算例里只有纯负荷没有光伏也没有储能所以复现时要人为设置若干个产消者节点。比如在节点8接入一个光伏容量大的产消者在节点25接入一个负荷波动大的产消者每个产消者配一组储能参数。这样改造之后标准算例就变成了一个“新型城镇配电系统”的仿真环境。我个人体会是先把IEEE33节点的潮流程序写通再去接博弈模型性价比最高。如果潮流问题不解决后面目标函数再怎么建结果都是不可信的。我复现过程中是先把节点导纳、潮流方程、二阶锥松弛都验证无误之后才开始往上叠加电价变量和产消者决策变量。2. 数学模型拆解双层博弈怎么变成可求解的单层问题2.1 上层配电系统运营商的最优定价模型上层是领导者决策变量就是一组24小时的分时电价。这里要区分两个价格运营商卖给产消者的电价以及运营商从产消者那里收购电的价格。在项目对应的论文里这两个价格通常都设为决策变量分别用λ_sell和λ_buy表示。上层问题的目标函数是运营商收益最大化从产消者那里收售电费减去从产消者那里买电花的钱再减去从上级电网买电的成本。有人可能会问运营商不是可以随意定价吗那它为什么不把价格定到天上去因为模型上有边界约束——论文会给电价设置上下限比如允许电价在基准电价的0.5到1.5倍之间波动。下层产消者也会根据电价调整自身行为如果电价定太高产消者会大幅减少购电、增加售电运营商反而赚不到钱。这就是博弈的联动性上层不能一厢情愿。这里还有一个隐藏在代码里的难点运营商的收益函数里有电价变量和电量变量相乘的项电价变量是上层的电量变量是下层的这导致两层问题耦合在一起不能拆开单独求解。这就是为什么不能简单地在Matlab里定义两个优化问题交替求解就完事必须做数学变换。代码层面运营商目标函数在YALMIP里的写法大致是这个结构% T是时段数Np是产消者数量 % C_buy, C_sell是电价决策变量P_buy, P_sell是产消者购售电量决策变量 Objective_dso sum(sum(C_sell .* P_buy - C_buy .* P_sell)) ... - C_upstream * P_upstream; % 扣掉从上级电网购电的成本 Constraints_dso [C_buy_min C_buy C_buy_max, ...];单看这行表达式很直白但你要意识到P_buy和P_sell并不是上层可以直接控制的变量它们来自下层产消者的最优解。上层要算收益就必须把下层行为“预测”出来。这就是双层规划的核心矛盾。2.2 下层产消者的用电与发电联合优化每个产消者本质上在求解一个带储能的家庭能量管理问题给定一组分时电价决定每小时从配网购多少电、卖给配网多少电、储能充多少放多少、光伏出力怎么分配。目标函数是产消者总成本最小化也就是购电费用减售电收入再加储能运行损耗成本。产消者的约束条件通常包括功率平衡约束光伏出力加购电加储能放电等于负荷加售电加储能充电、储能SOC递推约束SOC跟随充放电功率变化、SOC上下限约束、购售电功率上限约束以及光伏出力的可调范围。这里的关键在于这些约束全部是线性的目标函数也是线性的所以下层问题是一个线性规划。线性规划有什么好处好处太大了。线性规划的KKT条件是全局最优的充要条件这意味着我们可以把下层的求解逻辑完整地等价成一个约束方程组塞进上层问题里。这就为“双层转单层”提供了数学基础。如果你建模时把下层问题变成非线性问题比如储能充放电效率定义成分段函数或者光伏逆变器模型里加进非线性项那KKT条件就不再是充分条件整个模型的理论基础都得换。我在复现时特意确认了原论文里的效率参数用的是常量确认了才敢继续往下做。这是第一个值得记下来的经验复现博弈论文第一步不是看代码而是确认下层模型有没有避开非线性。2.3 KKT条件与大M法把双层问题压平双层问题直接求解非常困难通用做法是把下层问题用KKT条件替换掉变成单层的带均衡约束的数学规划问题。具体分三步走。第一步写出下层产消者线性规划问题的拉格朗日函数列出KKT条件包括平稳性条件、原始可行性、对偶可行性、互补松弛条件。这一步的工作量取决于下层约束的数量约束越多KKT条件式子越多代码里的变量也就越多。第二步把互补松弛条件线性化。互补松弛条件一般长这样某对偶变量乘以某不等式约束的松弛量等于0。两个非负量相乘等于0这是非线性约束。解决办法是引入二元变量配合一个大M常数把乘积为0的约束拆成两个不等式。这是标准的“大M法”M的取值很关键。M太小可能压制了解空间M太大会导致数值稳定性变差CPLEX求解时容易出现警告。我实测下来M取电价上限的10倍到20倍是一个比较稳的经验区间具体还要配合求解器反馈的参数调整。第三步把下层产消者的目标函数通过强对偶定理实现等价替换与上层目标函数合并。因为下层问题线性且可行强对偶成立所以下层的购电成本等于其对偶目标值可以代入上层问题最终把整个双层模型转成一个混合整数线性规划。这个单层模型就可以交给商业求解器直接求全局最优解。下面这个示意图逻辑就是完整的单层化流程原始双层问题 |--- 上层运营商定价模型 |--- 下层产消者响应模型 |--- 下层KKT条件替换 |--- 互补松弛条件大M线性化 |--- 强对偶消除下层目标函数 v 单层混合整数线性规划 - 商业求解器求解这个转化过程听上去是数学家在纸上推公式就能完成的事但落地到代码里每一步都有容易出错的地方。最典型的是对偶变量和原变量的下标对应关系稍微错位一个节点编号结果就会差十万八千里。我建议第一次做的人把KKT条件的每一行都用中文注释标清楚“哪个对偶变量对应哪条约束、节点是哪个产消者”后面排错能省一半时间。3. 代码实现全流程Matlab Yalmip Cplex的每一步3.1 环境配置与工具链选择复现这套代码我推荐的环境组合是Matlab YALMIP CPLEX或Gurobi。Matlab负责数据处理和结果可视化YALMIP是建模工具箱负责把优化问题写成高层的数学模型CPLEX是底层求解器负责真的去求解这些混合整数规划问题。YALMIP最大的价值在于它允许你用接近数学语言的语法描述优化问题不需要手动处理求解器输入格式。比如你想声明一个变量、定义一组约束、设定目标函数直接写参数名和表达式就行。这对科研人员来说极大地降低了编程负担把精力留在建模而不是底层的矩阵拼装上。安装方面有几个细节容易踩坑。第一YALMIP要从官方Git仓库下载最新版不要用老版本否则对二元变量和指数锥的支持可能不完整。第二CPLEX需要注册学术账户并下载对应操作系统的版本安装完之后要在Matlab里运行路径设置把安装目录加入Matlab搜索路径。第三YALMIP对求解器的自动检测有时候会失败出现“No suitable solver”的提示这时需要手动用sdpsettings指定求解器比如ops sdpsettings(solver, cplex, verbose, 2); ops.cplex.mip.tolerances.mipgap 1e-4;如果机器上没装CPLEX也可以用Gurobi替代。两个求解器的MILP求解能力都很强对IEEE33节点量级的模型完全够用。这个项目的问题规模其实并不大决策变量大概几千个、约束式上万条属于中小规模优化问题CPLEX通常几秒钟就能求解一轮。3.2 IEEE33节点与负荷数据准备IEEE33节点的数据在网上流传版本很多大体一致但细节有差异。复现前最好先花半小时把数据对齐否则后面对比结果时会出现莫名其妙的偏差。数据包含两部分节点数据每个节点的有功、无功负荷和支路数据每条支路的电阻、电抗、连接关系。原始数据通常是有名值即电阻电抗的单位是欧姆负荷单位是千瓦和千乏。在优化模型里为了数值稳定建议做标幺化处理。取基准容量10MVA、基准电压12.66kV功率基准和阻抗基准就固定下来了。标幺化之后各变量的数量级都在0.001到10之间求解器的数值稳定性会好很多。我这里给出一段IEEE33节点数据构造代码的骨架实际使用时要补全33个节点的完整数据% IEEE33节点基准值 S_base 10e6; % 10MVA V_base 12.66e3; % 12.66kV Z_base V_base^2 / S_base; % 阻抗基准 % 支路数据示例前端节点、末端节点、电阻标幺值、电抗标幺值 branch [ 1, 2, 0.00531, 0.00390; 2, 3, 0.00205, 0.00185; 3, 4, 0.00481, 0.00203; % 其余支路数据按标准数据表补齐 ]; % 节点负荷数据示例节点编号、P_load标幺值、Q_load标幺值 bus_load [ 1, 0, 0; 2, 0.001, 0.0006; % 其余节点数据按标准数据表补齐 ];产消者并不是每个节点都放一个实际操作是在33个节点里选取若干有代表性的节点作为产消者接入点。比如一个配3组储能、三种光伏容量的典型场景可能是节点8、节点25和节点32各配一组其余节点保持纯负荷状态。产消者的光伏曲线和负荷曲线可以用典型日曲线数据或者用正弦函数加噪声的方式生成仿真曲线。3.3 核心代码结构与关键片段整个代码的主流程可以按下面的步骤组织加载配电网数据定义决策变量写出约束条件写出目标函数调用求解器后处理并画图。决策变量的定义是整个模型最基础的部分。顺着前面数学模型的结构需要定义的电价变量是两组24维向量每个产消者的购售电量是24乘产消者数量的矩阵储能SOC是25乘产消者数量的矩阵。注意SOC多一行因为初始时刻的SOC要单独给定。我习惯把变量定义单独写一个section用中文注释标明每一个变量的物理含义和维度这样后续查错效率最高。关键代码片段如下%% 变量定义 T 24; % 仿真时段 Np 3; % 产消者数量 C_sell sdpvar(T, 1); % 运营商售电价 C_buy sdpvar(T, 1); % 运营商购电价 P_buy sdpvar(T, Np); % 产消者购电量 P_sell sdpvar(T, Np); % 产消者售电量 SOC sdpvar(T1, Np); % 储能荷电状态 P_ess sdpvar(T, Np); % 储能充放电功率正为充电负为放电 P_pv sdpvar(T, Np); % 光伏实际出力 P_upstream sdpvar(T, 1); % 从上级电网购电功率 %% 约束条件 Constraints []; % 产消者功率平衡约束 for k 1:Np for t 1:T Constraints [Constraints, P_buy(t,k) P_ess(t,k) P_pv(t,k) P_load(t,k) P_sell(t,k)]; end end % 储能SOC递推 eta_ch 0.95; eta_dis 0.95; Cap 500; % 储能容量 for k 1:Np for t 1:T Constraints [Constraints, SOC(t1,k) SOC(t,k) eta_ch * P_ess(t,k) / Cap]; Constraints [Constraints, P_ess(t,k) -P_dis_max(k), P_ess(t,k) P_ch_max(k)]; end end % 电价上下限 C_max 1.2; C_min 0.3; Constraints [Constraints, C_min C_sell C_max, C_min C_buy C_max];这段代码只是整个问题的冰山一角。完整实现时还要加上二阶锥潮流约束、大M线性化的互补条件、对偶变量定义等大量内容。但变量定义的思路就是这个样子把你关心的所有物理量全部声明成YALMIP的sdpvar对象然后一条一条往Constraints里堆约束。3.4 仿真结果怎么看、怎么核对代码跑通之后第一个要画的是电价和负荷的联合曲线。把24小时的运营商售电价、购电价、产消者购电量、售电量画在同一张图上先做个目测判断高峰时段的电价是不是明显高于低谷产消者是不是在电价高的时段倾向于放电和卖电在电价低的时段倾向于买电和充电。这个规律如果没体现出来说明模型逻辑有bug。第二个要核对的是储能SOC曲线。SOC的合理范围应该在设定的上下限之间并且应该是连续平滑变化不会出现断崖式跳变。如果SOC曲线突然出现不连续的大跳变最大的嫌疑是SOC递推约束里的充放电功率没有乘以时间尺度或者充放电效率应用方向写反了。第三个要核对的是潮流结果。配电网节点的电压幅值应该在0.95到1.05标幺值之间。这个核对有一点隐蔽因为很多代码如果只看目标函数和购售电结果分布式光伏接入后节点电压可能越限但模型里因为加入了潮流约束这个问题会被自动规避。如果电压越限说明潮流约束没有正确生效最常见的错误是DistFlow线性化漏掉了一个二次项。我习惯在最后做一个守恒性检查全网总购电量加上光伏总出力减去全网总负荷再算上网络损耗应该等于储能总充电量减放电量。这一条如果对不上说明功率平衡约束有漏项。4. 复现过程中的常见坑与排查思路4.1 求解器报错的常见原因复现过程中最让人崩溃的一件事就是YALMIP一言不合报错“Infeasible problem”。明明模型看起来没啥问题但求解器直接说不可行。这时候不要慌三个地方优先排查。第一个是约束条件的维度是否匹配。YALMIP对矩阵约束的维度要求很严格如果某个约束左边是24维向量右边是24乘3矩阵就会触发隐式扩张导致约束数量爆炸出现莫名其妙的不可行。排查办法是把每个约束的size打印出来用size(Constraints(1))这种命令逐一检查。第二个是数据是否做了标幺化。负荷单位是千瓦电价单位是元每千瓦时如果不统一到同一数量级约束系数矩阵的条件数会变得很差求解器在数值层面判断可行或不可行的根基就不稳。标幺化之后所有变量的正常波动范围基本都在0.001到100之间这个范围对CPLEX来说很友好。第三个是二元变量配合大M常数时M值选取不当。如果M设得过大比如1e9求解器在数值精度上会直接把M当成无穷大处理导致互补约束失效。如果M过小又可能把真实最优解切掉。我一般先用最大值的一个估计求解成功后逐渐减小M观察结果有没有变化这个操作在工程上叫参数灵敏度分析。4.2 不收敛或者收敛到“假均衡”怎么处理这可能是复现博弈类论文最难过的一关。理论上主从博弈通过单层化后用MILP求解是可以拿到精确解的但有些论文实现用交替迭代法即上层出价、下层响应、上层再调价这种循环方式。这时就可能出现两种问题迭代震荡不收敛或者收敛到一个看似稳定但实际不是真均衡的解。迭代不收敛的原因通常是上下层模型之间的步长没有设置好。比如运营商每次把电价调整幅度设得过大产消者响应也剧烈那两层之间就容易形成“过调”震荡。解决办法是引入阻尼系数也就是每次只更新一部分% 简单阻尼更新 alpha 0.5; C_sell_new C_sell_old alpha * (C_sell_candidate - C_sell_old);另一种“假均衡”是指迭代停了但目标函数值并不是全局最优。常见原因是初始电价设置太差算法陷入局部均衡。解决思路是多设几个初始点重跑比如从0.3、0.6、1.0三个不同的初始电价分别开始迭代看最后是否收敛到同一组结果。如果三个初始点都收敛到同一个解才能有比较大的把握说结果可信。4.3 结果对不上论文数值时的定位技巧很多同学复现完发现自己的数字和论文对不上就开始怀疑代码写错了。其实数字对不上非常正常因为论文里往往没有公布全部参数。比如储能的初始SOC是0.2还是0.5论文可能只字未提但这个参数对结果影响巨大。我先教你一个思路把结果差异分类。如果每条曲线的形状都和你论文里的一致只是数值整体偏移那多半是某个基准值或者价格单位不一致导致的属于系统误差。如果曲线形状都不同那就是模型结构的问题比如目标函数少了一项或者约束方向写反了。曲线形状对上了接下来就调参数。我建议把储能容量、光伏出力上限、电价上下限这几个参数各做一个简单的灵敏度分析比如分别乘以0.8、1.0、1.2观察结果的变动方向是否符合直觉。产消者储能容量变大电网的总购电量应该下降电价上限更高运营商的收益空间应该更大。如果灵敏度方向都不对那代码里必然有bug。最后一个笨办法是拆模型。把模型退化成一个简单的单层优化问题比如把下层产消者问题单独拿出来求解手动给定一组低价和高价看它做出的购售电决策是否合理。如果单独验证正确再放进双层里这样可以一步步缩小排查范围。5. 从复现到改进这篇代码还能怎么扩展5.1 模型维度的扩展方向最直接的扩展是加不确定性和鲁棒优化。原来的模型假设光伏出力和负荷曲线是已知的确定值实际情况中光伏出力受云层影响很大负荷也有波动。可以把光伏出力和负荷建模为不确定集合改成两阶段鲁棒优化上层决定电价下层在最坏场景下做决策最后用列与约束生成算法求解。这会显著提升论文的层次感。第二个扩展方向是加入多个类型产消者的差异性。原模型里所有产消者只有参数不同本质上还是同一类模型。你可以将产消者分成家庭型、商业型、工业型三类家庭型以光伏加储能为主商业型以可控负荷为主工业型以大型储能和可中断负荷为主。三类产消者的约束结构不同目标函数权重不同博弈会更加丰富。第三个扩展方向是考虑配电网重构。IEEE33节点原本带有联络开关可以通过改变网络拓扑来优化运行。你可以把拓扑变量也纳入上层优化让运营商的定价策略和拓扑调整协同优化。这个扩展对算法能力要求更高因为拓扑变量是离散的模型会从MILP变成混合整数二阶锥规划。5.2 算法维度的替代方案如果不想用商业求解器想采用更轻量的分布式算法可以考虑交替方向乘子法或原始对偶算法。这些算法的优势是可以保护各产消者的隐私不需要把所有人的数据集中到一个中心节点。在真实的城镇配电系统中不同用户的数据集中到一个平台会涉及隐私问题分布式优化是当前的研究热点。还有一条思路是强化学习。把上层运营商当成智能体用深度强化学习学习最优定价策略下层产消者继续用优化模型做最优响应。这种“学习中包含优化”的框架可以处理电价规则更加复杂、难以用解析KKT条件表达的场景但目前还处在研究前沿稳定复现的难度比较高。考虑到项目标题含“EI复现”核心追求还是可验证和可复现我建议先保证经典模型能吃透再谈算法创新。很多创新点其实是在经典模型上做局部改动改动越小越容易出成果也越容易通过审稿人的验证。5.3 算例维度的替换思路IEEE33节点跑通之后很多人会想换成更大规模的算例比如IEEE123节点或某个实际园区拓扑。这里提醒一句不要直接替换数据文件就完事。大算例的难点不在节点数量上而在参数管理和潮流初始化。一个务实的做法是先保持33节点的结构但在产消者的数量和接入位置上做变化。把产消者数量从3个增加到比如7个观察系统的均衡电价和负荷曲线有什么变化。这种做法对代码改动量小、实验效果好而且能讲一个“多产消者竞争对市场力的影响”的故事比单纯换大算例更有学术价值。等到算例规模确实需要提升就要注意求解时间。33节点系统的单层模型几秒钟就能解完但换到123节点可能会变成几分钟甚至更久。优化方向有两个一是通过分析稀疏结构只保留与产消者节点相关的潮流约束二是把部分约束用线性化近似替代牺牲一点精度换求解速度。5.4 实际工程落地时的注意点代码能跑通是第一步模型有工程价值是第二步。把主从博弈模型往真实系统落地时有几个问题需要提前考虑。电价信号在真实系统里不可能是24维连续可调变量实际发布的分时电价通常是峰平谷三段。所以严格来说论文里的模型是理想化的定价策略工程落地时要把最优电价聚类成三到四个档位这就需要对最优解做后处理聚类分析。另一个问题是通信可靠性。主从博弈的迭代机制假设双方都能精确获得对方信息但实际配电网的通信链路带宽有限且不可靠。如果产消者上报的数据有延迟或丢失迭代过程就不一定收敛。这个问题虽然不是论文复现的核心但在答辩和做工程汇报时经常会被评委问到提前想好答案会显得思考深入。还有网络安全层面的考虑。电价信号在传输过程中如果被伪造产消者可能会故意提交假数据让运营商做出错误的定价决策。这种数据和价格之间的信任问题已经有人开始用区块链技术做研究。你如果对交叉领域感兴趣这也不失为一个创新方向。我个人在实际操作中的体会是复现一套博弈类论文的代码真正的收获不在于最终跑出来的那几幅图而在于把双层优化的建模路径彻底打通。这就像学游泳在水里呛过几口水上岸后就知道哪里水深、哪里水浅。往后你看任何一篇带博弈模型的电力论文只要扫一眼领导者和跟随着的变量设定就能立刻判断出它后面走的是KKT单层化路线还是迭代寻优路线这本身就是科研功夫里很重要的部分。最后再分享一个小技巧每完成一个阶段性版本就用git打一个标签并且把关键参数复制出来保存成固定的输入文件。复现博弈论文通常要反复调参没有版本管理很容易改乱。我吃过这个亏调了两天参之后结果突然全部变化就是因为一个M值在某个函数里被覆盖了。有了版本标签随时可以回到之前的稳定状态这个习惯对我来说帮了大忙。