ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

考虑源荷不确定性的含风电电力系统低碳调度程序设计与实现

考虑源荷不确定性的含风电电力系统低碳调度程序设计与实现 说句实在话电力系统经济调度这个方向这几年最大的变量就是“不确定性”和“低碳”这两个词同时压过来。我最近刚把一个“考虑源荷不确定性的含风电电力系统低碳调度程序”完整跑通从问题建模、场景生成到Yalmip求解、结果分析整个过程踩了不少坑也攒了不少心得。这篇文章就是给正在做相关课题的研究生和工程师看的主要内容包括风电和负荷不确定性怎么用场景法建模、碳交易机制怎么嵌入调度目标、MATLAB代码的完整框架怎么搭以及我在调试过程中遇到的典型问题和解决办法。如果你是刚接触这个方向的初学者先放心把文章读完我会从数学建模一路讲到代码实现和调试尽量做到让你拿着就能改、改完就能跑。1. 先搞清楚要解一个什么样的优化问题1.1 风电一多调度就从“确定性”变成“随机规划”传统经济调度面对的是确定的负荷曲线和确定的机组参数核心问题就是“在满足负荷的前提下怎么安排各台火电机组的出力让总煤耗成本最小”。这个问题本质上是一个确定性优化问题变量是各机组的出力约束是功率平衡和机组极限求解器一把梭就能搞定。但风电一进来情况就变了。风电出力不是你能控制的它取决于风速而风速的预测天然带有误差。今天预测晚上8点出100MW实际可能只出60MW也可能出到130MW。这就导致一个很现实的问题调度方案必须留有余地不能把系统卡得太死否则风一停火电来不及顶上频率就崩了。更麻烦的是风电还有个“反调峰”特性。很多风电场夜间的出力比白天还高而夜间恰好是负荷低谷。也就是说系统需要火电出力最小的时间段恰恰是风电出力最大的时候这会造成大量的弃风。反调峰不是数学问题是物理事实建模的时候必须正面处理。负荷侧同样有不确定性。虽然负荷预测比风电预测准不少但节假日、极端天气、大用户临时启停都会造成偏差。如果调度模型完全按预测值来算实际运行时就可能面临功率不平衡。所以这个题目的本质是把原来那个确定性优化问题升级成一个“源荷双侧都存在随机扰动”的随机规划问题。具体到代码层面通常采用的是两阶段随机优化框架第一阶段的决策是火电机组的启停计划必须在看到风电实际出力之前定下来第二阶段的决策是各机组出力可以根据风电实际出力做调整。程序里要体现这种“先决策、后修正”的逻辑而不是把所有决策变量都放在同一个篮子里。1.2 “低碳调度”本质上是在目标函数里给碳排放定价低碳调度不是简单地在约束里加一条“总碳排放不超过某个上限”。那样做当然也是可行方案但过于刚性——万一系统确实需要多排一点来保供电呢实际工程和学术论文里更常见的做法是把碳排放纳入经济性考量也就是引入碳交易机制。碳交易机制的原理可以类比成“排污权交易”政府给发电企业发放一定量的免费碳排放配额企业实际排放如果低于配额富余部分可以在市场上卖掉获利如果超过配额就必须掏钱购买超额部分。这样一来碳排放就从“约束”变成了“成本”火电成本越高、碳排放强度越大的机组在优化中就越是会被压低出力低碳的机组则获得竞争优势。我在这个程序里采用的是阶梯碳价机制也就是超额排放量越大单位购买价格越高。这个设计非常贴近国内碳市场的实际规则能在模型里逼着系统在“多用便宜高碳煤电”和“少排但多花钱”之间做一个更精细的权衡。所以这个程序要解的优化问题用一句话说就是在满足负荷和备用需求的前提下考虑风电和负荷的随机波动统筹火电燃料成本、启停成本、碳交易成本和弃风惩罚找到一个总期望成本最小的机组组合与出力方案。这是一个混合整数规划问题——整数变量来自机组启停和阶梯碳价分段选择连续变量来自出力、风电实际出力和碳交易量。2. 不确定性建模场景法缩减的完整实操2.1 为什么我选择用场景法而不是直接把误差写成鲁棒区间处理不确定性的主流方法有三种概率场景法、鲁棒优化和区间优化。区间优化最简单但结果偏保守而且很难跟阶梯碳价这种非线性机制结合。鲁棒优化能保证最坏情况下的可行性但需要把不确定集做对偶变换模型复杂度和求解难度都会明显上升尤其是跟低碳调度放在一起时稍不注意就会变成一个解不动的庞然大物。我最后采用的是场景法。场景法的思路很直观与其用一个确定的预测值不如生成一批可能的“未来副本”。比如用蒙特卡洛抽样生成500个风电出力曲线和500个负荷曲线每个曲线代表一种可能发生的源荷场景再给每个场景赋一个出现概率。调度目标就变成“所有这些场景下的期望总成本最小”。这就像天气预报说明天有70%概率下雨你出门就带伞。场景法就是把这个逻辑搬到电力系统里系统在每个可能发生的源荷场景下都要能安全运行而目标函数里则把每个场景的运行成本按概率加权求和。这个方法的好处是直观、易于实现而且可以直接复用确定性优化的大部分约束结构。2.2 蒙特卡洛生成初始场景的实操细节生成场景的第一步是确定误差分布。对于风电我取预测出力的10%作为标准差用正态分布在预测值附近抽样。这里必须注意两个边界风电出力不能为负也不能超过风电场装机容量所以抽样之后要做一次截断处理。rng(42); % 固定随机种子保证可复现 N0 500; % 初始场景数 T 24; % 调度时段数 Pw_fc wind_data.Pw_fc; % 24时段风电预测出力 Pw_cap wind_data.cap; % 风电场装机容量 % 风电场景预测值 正态扰动 Pw_raw Pw_fc .* (1 0.10 * randn(T, N0)); Pw_raw max(0, Pw_raw); % 下限截断 Pw_raw min(Pw_cap, Pw_raw); % 上限截断负荷场景的生成方式类似但误差取2%左右因为负荷预测比风电准得多。生成风电场景和负荷场景之后把两个矩阵纵向拼接形成一个 (2T \times N_0) 的联合场景矩阵。为什么要拼在一起因为风电和负荷的波动会同时影响功率平衡后续做场景缩减时也要对“源荷联合场景”统一处理分别缩减会导致概率分布失真。2.3 场景缩减K-means聚类与概率重置500个场景直接放进优化模型会导致决策变量爆炸。假设有3台火电、24时段每台机组的出力和启停在每个场景下都要重复定义变量数会轻松超过几万个整数变量一多求解器就罢工。所以必须做场景缩减。我实测对比过两种方法。第一种是K-means聚类MATLAB里直接调kmeans函数即可速度快代码少。第二种是同步回代缩减fast backward reduction保留极端场景的能力更强但实现麻烦计算也慢。对于这个课题来说K-means配合概率重置已经足够用。K 10; % 缩减后保留的场景数 % 转置kmeans要求每行是一个样本 [idx, C] kmeans(joint_scn, K, Distance, sqeuclidean, Replicates, 10); % 计算每个聚类簇的概率 prob histcounts(idx, (1:K1)-0.5) / N0; % 提取缩减后的场景 scn_red C; % 每列一个场景行数为 2T Pw_scn scn_red(1:T, :); % 风电场景 PL_scn scn_red(T1:2*T, :); % 负荷场景这里有个容易被忽略的点kmeans输入的行是样本列是特征所以要把原始矩阵转置再传入。Replicates参数设为10表示重复跑10次kmeans、取最优结果因为kmeans的初始中心是随机选的不设这个参数结果不稳定。还有一点经验之谈缩减后的场景数K不是越大越好。K5在趋势上够用、求解速度最快K10能明显改善结果的平滑性K20以上对结果精度提升有限但求解时间可能翻几倍。做课程设计或论文验证阶段K10是性价比最高的选择。2.4 场景概率在目标函数里怎么用场景缩减完之后每个场景带一个概率 (p_k)所有概率之和为1。在优化模型里目标函数要写成概率加权的期望成本形式[ \min ; \sum_{k1}^{K} p_k \cdot C_k(x_k) C_{\text{startup}}(u) ]其中 (C_k) 是第k个场景下的运行成本包括燃料成本、碳交易成本和弃风惩罚(C_{\text{startup}}) 是机组启停成本。注意启停成本不能按场景加权——因为启停决策是第一阶段变量在所有场景下必须一致。这一条在代码上要有明确的区分我在第4节里会给出具体写法。3. 目标函数与约束搭建碳交易模型的嵌入细节3.1 阶梯碳价的分段线性化别用整数变量会踩坑阶梯碳价是很多人在编写代码时纠结过的地方。我先把模型写出来假设系统实际碳排放量为 (E_{\text{actual}})免费配额为 (E_{\text{free}})。当实际排放超过配额时超额量 (E_{\text{excess}} E_{\text{actual}} - E_{\text{free}}) 要分段购买0~200吨部分按30元/吨200~400吨部分按45元/吨超过400吨部分按60元/吨。很多人第一反应是引入0-1变量判断超额量落在哪个区间然后加一大串 if-else 逻辑。这样做也能跑但数学上这是画蛇添足而且会让混合整数规划多一堆整数变量求解速度明显变慢。实际上阶梯碳价这种“单价递增”的分段函数是一个凸函数对于凸分段线性函数可以用增量变量直接建模不需要任何整数变量。原理很简单因为后面段的价格更贵目标函数在求最小值时模型会自动优先填满低价段绝不会出现“200段还没用完就直接用400段”的情况。E_excess E_actual - E_free; % 增量变量y(1)、y(2)、y(3) 分别对应三段超额量 y sdpvar(3, 1); % 超额量分解 C [C, E_excess y(1) y(2) y(3), 0 y(1) 200, 0 y(2) 200, 0 y(3) M]; % M为上界取足够大的数 % 碳购买成本 C_buy 30 * y(1) 45 * y(2) 60 * y(3);如果实际排放小于免费配额富余部分按30元/吨出售产生负成本收益。此时碳交易成本写成C_carbon C_buy - 30 * max(0, E_free - E_actual);max(0, ...)在Yalmip里不是线性表达式需要引入一个辅助变量。我的做法是定义变量E_sell加约束 (E_{\text{sell}} \ge E_{\text{free}} - E_{\text{actual}})、(E_{\text{sell}} \ge 0)然后把 (-30 \cdot E_{\text{sell}}) 放进目标函数。这样既保持了线性又不会出现两个方向的碳收益同时被计算的问题。3.2 目标函数每一项的含义与量纲目标函数是整个程序的心脏我拆成四块说明。第一块是火电燃料成本。对第 (i) 台机组、第 (t) 个时段燃料成本写成二次函数 (a_i P_{i,t}^2 b_i P_{i,t} c_i u_{i,t})其中 (u_{i,t}) 是启停状态0-1变量(c_i) 是空载成本只有机组运行时才计入。用Yalmip表达fuel_cost sum(sum(gen.a .* P.^2 gen.b .* P gen.c .* u));注意P是第二阶段变量在场景循环内定义每个场景算一次燃料成本再乘概率。第二块是启停成本。启动成本只在机组从停机变为运行的时刻产生不能用max(0, ...)这种非线性写法而是引入辅助0-1变量v_start binvar(nG, T, full); C [C, v_start(:, 2:T) u(:, 2:T) - u(:, 1:T-1)]; v_start(:, 1) u(:, 1); % 第一时段若开机则计启动 startup_cost sum(sum(SU .* v_start));这里有个细节启动成本矩阵SU是每台机组一个值不是每时段一个值所以要广播到同样形状再点乘。第三块是碳交易成本。上面已经给出表达式这里不再重复。需要提醒的是碳交易成本在场景循环内部计算因为每个场景的火电出力不同碳排放量也不同。第四块是弃风惩罚。弃风惩罚用来量化“明明有风却不得不弃掉”的损失保证优化不会为省一点煤耗而无节制地弃风。表达式为curtail_cost lambda_cur * sum(sum(Pw_avail - Pw_used));Pw_avail是场景实际可用风电Pw_used是调度实际采用的风电出力。弃风惩罚系数的量纲是元/MWh取值不低于风电边际价值我一般取80~100元/MWh。3.3 约束条件的完整清单把约束条件列清楚代码才不会改乱。我在程序中用到的约束如下功率平衡约束每个场景、每个时段火电出力 风电实际出力 负荷。这是等式约束也是最核心的约束。机组出力上下限约束火电出力必须在最小技术出力与最大出力之间同时受启停状态控制。爬坡约束机组相邻时段出力变化不能超过爬坡速率这决定了火电跟踪风电波动的能力。旋转备用约束系统要有足够的向上备用容量我取负荷的5%加上风电预测出力的10%作为备用需求。风电出力约束调度采用的风电出力不能超过该场景的可用风电出力。碳排放量与碳交易约束碳排放量根据各机组出力和碳排放强度计算代入阶梯碳价的增量模型中。其中爬坡约束是一个很典型的易错点。如果火电出力变量不分场景爬坡约束很好写但一旦把出力变量按场景拆开爬坡约束就变成“同一个场景内部相邻时段之间的出力差约束”不能跨场景约束。逻辑上也很容易理解你不可能在场景A里按爬坡上限运行下一刻切换到场景B的出力方案这不是物理上可实现的运行轨迹。4. MATLAB代码逐段拆解从数据到求解4.1 文件组织与数据准备我的程序文件结构分成四块data目录存放所有输入数据scenario目录存放场景生成与缩减代码model目录存放优化模型构建代码result目录存放结果输出与绘图代码。这种拆法不是为了好看而是因为这类程序的调试周期通常很长——你改数据、改场景、改模型如果所有代码都堆在一个文件里三天后你自己都看不懂自己写了什么。数据准备阶段我给了一个3台火电、1座风电场、24时段的示例系统。火电机组参数如下表机组Pmin/MWPmax/MWa/(元/MW²h)b/(元/MWh)c/(元/h)爬坡/(MW/h)碳排放强度/(t/MWh)G1502000.02015100400.90G2301500.0351880350.85G3201000.0502260300.95负荷曲线按典型的双峰日负荷设置峰荷出现在11:00~20:00约680MW低谷在凌晨3:00~5:00约420MW。风电预测曲线设置为典型的夜间大风、白天小风形态装机容量150MW。碳市场参数设置为免费配额按系统总负荷的基准排放强度计算基准强度取0.7吨/MWh基础碳价30元/吨阶梯阈值200吨和400吨。这些数据都在data/load_data.m里以结构体形式存储主程序直接调用。我强烈建议所有输入参数都集中在一个脚本里不要在模型构建代码里硬编码数值——我试过在约束里直接写死一个1000后面想调参数时花了半小时才找到在哪里。4.2 场景生成与缩减代码的完整写法场景生成的代码在第2节已经给出核心片段这里补充一个“源荷联合缩减”的完整版本。注意两个矩阵拼接时行数要对应好否则缩减后风电和负荷数据会错位。function [Pw_scn, PL_scn, prob] generate_scenarios(wind_data, load_data, N0, K) T length(load_data.forecast); rng(42); % 1. 风电抽样 Pw_raw wind_data.forecast .* (1 0.10 * randn(T, N0)); Pw_raw max(0, min(wind_data.cap, Pw_raw)); % 2. 负荷抽样 PL_raw load_data.forecast .* (1 0.02 * randn(T, N0)); PL_raw max(0, PL_raw); % 3. 纵向拼接形成联合场景 joint [Pw_raw; PL_raw]; % 4. K-means 缩减 [idx, C] kmeans(joint, K, Distance, sqeuclidean, Replicates, 10); % 5. 计算每个场景概率 prob histcounts(idx, (1:K1)-0.5) / N0; prob prob(:); % 6. 拆分场景 scn_red C; Pw_scn scn_red(1:T, :); PL_scn scn_red(T1:2*T, :); end4.3 主程序两阶段决策变量的定义与目标函数构建主程序的核心是把“第一阶段启停变量”和“第二阶段出力变量”分开定义。第一阶段变量不随场景变化第二阶段变量用cell数组按场景存储。nG length(gen); T 24; K size(Pw_scn, 2); % 场景数 % 第一阶段机组启停对所有场景一致 u binvar(nG, T, full); v_start binvar(nG, T, full); % 第二阶段出力按场景索引可随场景调整 P cell(K, 1); Pw cell(K, 1); for k 1:K P{k} sdpvar(nG, T, full); % 火电出力 Pw{k} sdpvar(1, T, full); % 风电实际出力 end % 目标函数 objective startup_cost(u) mission_cost; % 先声明变量占位 for k 1:K Pk P{k}; Pwk Pw{k}; PLk PL_scn(:, k); % 燃料成本 fuel sum(sum(gen.a .* Pk.^2 gen.b .* Pk gen.c .* u)); % 碳排放与实际排放 E_actual sum(sum(gen.carbon_intensity .* Pk)); % 简化按出力线性累计 % 碳交易成本 carbon_k carbon_cost(E_actual, carbon_param); % 弃风惩罚 curtail lambda_cur * sum(sum(Pwk)) - sum(sum(Pwk)); % 注意这里是示意 % 场景加权 objective objective prob(k) * (fuel carbon_k curtail); endv_start与u的关联约束要加进去这部分的代码参考3.2节。carbon_cost是单独写的函数内部实现阶梯碳价的分段线性化参数通过结构体传递。我这样设计有一个好处如果想改成碳税模型、配额交易模型或者免费配额逐年递减模型只需要改这一个函数主程序完全不用动。4.4 约束构建与求解器配置约束同样在场景循环里逐场景添加。最核心的功率平衡约束和备用约束C []; for k 1:K Pk P{k}; Pwk Pw{k}; PLk PL_scn(:, k); % 功率平衡 C [C, sum(Pk, 1) Pwk PLk]; % 机组出力上下限 C [C, repmat(gen.Pmin, 1, T) .* u Pk repmat(gen.Pmax, 1, T) .* u]; % 风电出力不超过可用风电 C [C, 0 Pwk Pw_scn(:, k)]; % 爬坡约束场景内相邻时段 C [C, -gen.ramp Pk(:, 2:T) - Pk(:, 1:T-1) gen.ramp]; % 旋转备用约束 reserve_req 0.05 * PLk 0.10 * Pw_scn(:, k); C [C, sum(repmat(gen.Pmax, 1, T) .* u, 1) PLk reserve_req]; end求解配置上我优先使用GurobiYalmip代码不变只需设置求解器参数ops sdpsettings(solver, gurobi, ... gurobi.MIPGap, 0.01, ... gurobi.TimeLimit, 600, ... verbose, 2); diagnosis optimize(C, objective, ops);MIPGap设为0.01意味着求解器找到与上界相差1%的解就停止这是工程上常用的做法。默认的0会迫使求解器证明全局最优在几十万变量的模型上可能要多等几个小时而这1%的差距对调度结果的影响几乎可以忽略。如果你没有Gurobi的licenseCplex也可以代码改为cplex.MIPgap即可。如果两者都没有MATLAB自带的intlinprog也能解但求解速度会慢很多中小规模测试尚可场景数一多就不推荐了。5. 调试与提速求解器和模型层面的真实经验5.1 求解时间爆炸的三大元凶这个程序从“能跑”到“跑得快”中间隔着一堆经验。我总结出三个最常见的性能杀手。第一个是整数变量过多。机组启停变量是 (nG \times T) 个这是模型结构决定的无法削减但阶梯碳价如果错误地使用0-1变量做区间判断会额外增加 (3 \times T) 个整数变量完全没必要。用凸分段线性化规避整数变量是提速的第一招。第二个是不合理的“大M”取值。很多人在写约束时喜欢用M 1e6甚至1e9这种“绝对够大”的数这会让线性规划的数值条件变得很差求解器内部的预处理和割平面效率急剧下降。正确的做法是给每个大M取一个紧上界。比如碳交易超额量的上界可以用“所有机组满出力时的总排放量减去免费配额”来算这才是这个变量的物理上界。第三个是场景数设得太多。K50以上的场景研究看起来“更严谨”但变量规模是线性增长的求解时间是超线性增长的。我做了一组对比测试同一个模型K5时Gurobi 30秒解完K10时约4分钟K20时将近25分钟。如果你的目标是验证方法有效性而不是刷极端指标K10足够。5.2 结果不合常理的排查顺序跑出来的结果如果“看起来不对劲”先别急着怀疑求解器。我总结了一套排查顺序按这个顺序检查大多数问题都能快速定位。第一步检查功率平衡是否闭合。把各场景下火电出力加风电出力减去负荷画出来差值应该是零。如果不是零八成是变量索引错位了比如把负荷矩阵转置写反或者场景矩阵行顺序对不上。第二步检查弃风率是否异常。如果弃风率高达80%以上先查一下是不是没加弃风惩罚项或者惩罚系数设成了0。这种情况下系统没有任何代价去消纳风电自然就全弃了。第三步检查碳价对结果的影响。把碳价从0逐步提高到100元/吨观察高碳排放机组的出力占比是否明显下降。如果碳价变化对结果毫无影响说明碳交易约束没接上或者免费配额设得太宽松系统根本没产生超额排放。第四步检查启停计划是否混乱。正常的机组组合结果应该是“白天多开、夜里少开”如果出现机组频繁启停、甚至同时启停检查一下启停成本是否设置过小爬坡约束是否过严。5.3 常见报错速查表报错信息或现象原因分析处理方法Yalmip报“No suitable solver”安装了Yalmip但没配置求解器路径或求解器license失效运行yalmiptest检查求解器状态重新添加路径Cplex报“MIQP不是凸问题”目标函数的二次项系数出现了负值导致非凸将二次项分段线性化或检查机组成本系数a是否为正Gurobi一直跑不到最优解MIPGap设成了0或模型规模过大设置MIPGap0.01和TimeLimit先用K5跑通全流程风电出力结果全等于预测值场景变量没用对约束里实际用的还是预测值检查功率平衡约束是否引用了Pw_scn而不是Pw_fc机组频繁启停启停成本被设成0或启动成本远低于启停造成的额外煤耗给启停成本赋合理值参考机组启停一次消耗的燃料费用求解结果总成本为负数目标函数里碳收益项被错误地无条件减去检查碳交易成本是否用了辅助变量和不等式约束确保收益只在富余时计算矩阵维度不匹配报错cell数组场景变量形状与约束表达式尺寸不一致统一用size(Pk, 1)和size(Pk, 2)推导不要写死数字5.4 一个真实的调试案例我遇到过最典型的问题是备用量约束写得太强导致模型在K10时最佳可行解的成本比未加备用约束时高了12%。当时我取的备用需求是负荷的10%加风电的20%结果风电大出力场景下系统被迫多开了一台火电来盯守备用容量成本飙升。后来我把备用公式调成“负荷的5%加风电出力的10%”并且只要求“向上备用”即上备用不再要求“向下备用”求解时间从28分钟降到6分钟成本也回到合理区间。这个案例说明备用约束的系数不是越大越安全而是要和系统规模匹配过大的备用需求会让经济性严重恶化。6. 后续扩展多目标、碳捕集与需求响应6.1 从单目标走向多目标帕累托前沿如果课题要求同时考虑经济性和低碳性可以把模型从单目标扩展为多目标。最稳妥的实现办法是epsilon约束法先单独求解最小总成本得到最优成本 (C^)最小碳排放得到最优排量 (E^)然后把碳排放设成一个逐步收紧的上限 (\bar{E})在每个 (\bar{E}) 下重新求解最小成本问题。把每一组 ((\bar{E}, C)) 画出来就得到了帕累托前沿。我的实现里只需要在主程序中加一个循环外层枚举 (\bar{E}) 的值内层调用原有的optimize函数。这个改动对代码结构几乎没有影响因为目标函数和约束本来就是模块化的。6.2 接入碳捕集与需求响应再进一步可以给火电机组加装碳捕集装置。碳捕集的核心是给机组增加一个“捕集能耗”项机组总出力分为上网出力和捕集装置用电两部分同时捕集率降低净碳排放。这个扩展需要对机组的出力变量和碳排放计算同时做修改在Yalmip里自由度很高但要注意非线性项会增多建议把捕集成本也做成分段线性函数。需求响应也是常见的扩展方向。把负荷中可转移的部分拆出来作为可调度变量放在功率平衡约束里同时给用户侧的负荷转移加一个舒适度惩罚成本。这个扩展说白了就是给负荷侧也加上“决策变量、约束和成本项”和火电机组的建模方式完全同构代码上不难实现。关键是要克制每加一个模块模型规模就膨胀一圈求解难度也指数级上升。我个人的建议是每次只扩展一个方向跑通并验证结果合理性之后再加下一个。一堆扩展功能堆在一起出问题时你根本不知道是谁引起的。如果要做鲁棒化改造最值得关注的是分布鲁棒优化方向也就是在场景法的基础上加一个Wasserstein距离球来界定场景分布的误差。这样能兼顾场景法的计算便利性和鲁棒优化对分布偏移的防御能力是当前学术研究的热点。不过实现复杂度比纯场景法高不少适合对Yalmip和优化建模都比较熟悉的阶段再考虑。结尾这套程序我前后调了两周才稳定跑通最深的体会是像这种“不确定性低碳”叠加的调度模型本质上不是“写”出来的而是“改”出来的。正确的路线是先跑通一个不含不确定性的确定性调度模型确认目标函数、约束和碳交易模块都没问题再引入场景法、加入随机变量。一步到位地把所有复杂度塞进代码调试时会像大海捞针。另外一个很实用的建议是拿到别人的代码包括网上下的、师兄师姐给的不要上来就改模型。先把原始数据替换成你自己的系统参数跑一遍确认程序能稳定复现结果再逐段加注释理解每个变量的形状和每一组约束的物理含义最后再做模型修改。如果你跳过前两步直接去改目标函数出问题的时候你根本分不清是原本的bug还是你自己的bug。最后分享一个小技巧把所有输入参数都用结构体封装并且在load_data.m里加一行assert检查参数取值合法性。比如风电场装机容量必须为正、场景概率之和必须等于1、各场景负荷不能为负。这些看似不起眼的检查能在你改坏数据时第一时间报警省下无数排查时间。
返回列表