ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于关键场景辨别与CCG的两阶段鲁棒微网优化调度Matlab实现

基于关键场景辨别与CCG的两阶段鲁棒微网优化调度Matlab实现 微网优化调度里不确定性是绕不开的坎。光伏、风电出力跟着天气走负荷也总有预测偏差如果按确定性模型做日前计划实际运行时经常要花大代价去调整严重时甚至切负荷。这篇内容聊的是一套基于关键场景辨别算法的两阶段鲁棒微网优化调度方法配套Matlab代码实现。它用“不确定集合”替代了传统随机规划对概率分布的依赖又通过关键场景辨识把计算规模压下来是工程上很实用的一种折中方案。适合谁看正在做微网能量管理、综合能源系统优化调度的研究生和工程师尤其是被两阶段鲁棒优化求解复杂度劝退、想找一个能跑通的Matlab参考实现的朋友。我会把模型怎么建、关键场景怎么辨、CCG算法怎么迭代、代码里哪些地方容易踩坑一层层拆开讲清楚。1. 问题背景与方案选型思路1.1 不确定性对微网调度意味着什么一个典型的微网包含分布式电源光伏、风机、燃气轮机、储能系统、可调负荷以及与大电网的交换联络线。调度的本质是在满足负荷需求的前提下最小化总运行成本。但问题在于光伏出力和风机出力是强随机量预测误差随天气剧烈变化负荷也有较大的波动如果微网参与电力市场甚至电价也是不确定的。举个最简单的例子日前预测明天10点负荷是100kW你按这个值安排机组出力和购电计划。但等到10点实际负荷涨到120kW或者光伏出力比预测少了15kW怎么办只能下调储能放电、增加与大电网的交换功率这些在实时阶段往往对应更高电价再不行就只能切负荷。如果提前知道“明天可能出这些意外”日前计划就会留出裕量比如让储能多存点电或者多预留可调机组容量。确定性调度的问题就在于它把预测值当成真实值没有给“意外”留位置。随机规划虽然能处理不确定性但它需要精确的概率分布而且为了保证精度往往要采样成千上万个场景计算量很大。两阶段鲁棒优化走的是另一条路只要求不确定量落在一个集合里不关心具体概率目标是让方案在所有可能场景下都可调、可行、成本可控。这个思路在实际微网调度中非常对味因为新能源出力的概率分布很难准确估计但波动范围反而比较容易确定。1.2 随机规划与鲁棒优化怎么选很多初学者会在随机规划和鲁棒优化之间犹豫。我的经验是先看你对不确定性有多少先验信息。如果历史数据充足、分布拟合效果好随机规划或分布鲁棒可以给出更好的期望成本如果数据有限、更在意方案在极端情况下的安全性两阶段鲁棒优化是更稳的选择。对比维度随机规划两阶段鲁棒优化不确定性描述概率分布不确定集合目标函数期望成本最坏场景成本决策风险可能低估极端场景影响偏保守但安全性高计算复杂度场景增多时显著上升取决于不确定集合结构和算法效率工程适用性分布信息充足时好对数据要求低适合新能源微网从数学结构上说两阶段鲁棒优化的原问题是[ \min_{\mathbf{x}} \left{ \mathbf{c}^T \mathbf{x} \max_{\mathbf{u} \in U} \min_{\mathbf{y} \in F(\mathbf{x},\mathbf{u})} \mathbf{d}^T \mathbf{y} \right} ]第一阶段决策 (\mathbf{x}) 代表日前的“慢决策”比如机组启停、与大电网的合约计划第二阶段决策 (\mathbf{y}) 代表在不确定量 (\mathbf{u}) 实现后的“快调整”比如各机组实际出力和储能充放电。外层 min 是让日前成本加最坏情况调整成本总和最小内层 max-min 就是在所有可能的不确定场景里找那个让调整成本最大的“最恶劣场景”再优化该场景下的运行方式。这种结构保证了方案在集合 U 内任何场景下都可行这就是鲁棒性的来源。2. 关键场景辨别算法核心思路拆解2.1 场景集怎么生成两阶段鲁棒的前提是构造一个合理的不确定集合 U。实际工程中U 通常由典型历史场景拟合得到。做法是先采样大量光伏、风机、负荷出力场景再用统计手段提取关键信息。常用的场景生成方法有三种蒙特卡洛采样按统计分布直接随机抽样简单但样本量大、冗余严重。拉丁超立方采样LHS把每个不确定量的分布等概率分层每层强制采样样本覆盖更均匀同样样本数下逼近效果更好。时间序列自回归模型对风速、光照、负荷的时序相关性建模适合需要日内连续出力曲线的场景。我的建议是先用 LHS 生成 500 到 2000 个场景作为“初始场景池”再从中做关键场景辨别。LHS 相比纯蒙特卡洛的好处是即使生成场景数不多也能覆盖到分布尾部这对后续捕捉最坏场景很有帮助。在Matlab里光伏出力一般用 Beta 分布拟合风速用 Weibull 分布拟合负荷用正态分布拟合。比如光伏出力的标幺值可以通过历史数据估计 Beta 分布的两个形状参数。不过这里要注意必须对每个调度时段分别采样因为一天 24 小时的光照、风速特性差异很大不能用一个全天平均分布来代替。2.2 关键场景辨别到底在做什么很多文章一上来就讲算法但没说清楚它解决的痛点。核心矛盾是直接对 1000 个场景做两阶段鲁棒优化计算代价极高因为内层 max-min 问题需要对每个场景都做一次对偶转换和求解而随机规划虽然也需要场景削减但通常只是用蒙特卡洛统计期望对“极端场景”不敏感。关键场景辨别算法的本质是把海量场景压缩成少量有代表性的“关键场景”然后用这些关键场景的包络或中心去构造不确定集合。它和普通聚类的差别在于普通聚类追求的是样本划分的紧凑性而关键场景辨别更关心两个目标代表性选取的场景要能覆盖整个场景空间的主要分布特征。边界性必须保留那些出力特别低、负荷特别高的恶劣场景因为两阶段鲁棒的最坏情况往往发生在不确定集合的边界。用生活化的类比你组织一次团队投票不能只选几个“平均水平”的同事代表所有人必须还要包含那几个总提反对意见、条件最苛刻的成员否则最终方案在真正的困难场合会出问题。关键场景辨别就是这个思路。具体实现上常见做法是分两步。先对场景做聚簇用 K-means、K-medoids 或者基于密度的聚类方法再从每个簇里挑出两类候选一是簇中心附近能够代表该簇典型特征的场景二是该簇内离中心最远、波动最极端的场景两者合并形成关键场景集合。这样既能保证典型性又能保住鲁棒优化需要的“边界形状”。很多论文里会直接用 K-means 聚类中心作为关键场景这套做法在随机规划里问题不大但在鲁棒优化里有个隐患聚类中心天然是“平均化”的极端场景在簇内占比小、对中心影响弱很容易被丢弃。所以我在自己的Matlab实现里会额外保留每个簇中的极值场景再联合做不确定集合构建。实测下来这样构造的 U 更贴合原场景池的边界鲁棒方案也更有代表性。2.3 从关键场景到不确定集合有了关键场景之后就要构造可供鲁棒优化使用的不确定集合 U。最基础的是盒式集合[ U \left{ u_t : \underline{u}_t \le u_t \le \overline{u}_t \right} ]其中 (\underline{u}_t) 和 (\overline{u}_t) 分别取所有关键场景中第 t 个时段的最小值和最大值。盒式集合最简单但把所有时段的不确定量都推到边界往往过度保守导致调度方案成本偏高。更实用的做法是引入“预算约束”。光伏预测出力 (\hat p_t)实际出力可表示为[ \tilde p_t \hat p_t \zeta_t \hat p_t, \quad \zeta_t \in [-\hat \zeta, \hat \zeta] ]然后加一个预算约束[ \sum_t \left| \zeta_t \right| \le \Gamma ](\Gamma) 相当于“最坏情况总量上界”。(\Gamma0) 时退化为确定性模型(\Gamma) 越大方案越保守。这样做的好处是不会让所有时段同时出现最大偏差更符合实际天气和负荷变化规律。预算值 (\Gamma) 的选取可以基于历史误差统计如果预测误差平均只有 15% 时段超过额定范围那 (\Gamma / T) 设 0.2 左右通常比较合理。我自己的调参经验是先跑一个 (\Gamma0) 的确定性基线再逐步增大 (\Gamma)观察成本曲线变化。如果成本从 1.0 万涨到 1.5 万但实际最坏场景下的切负荷量和弃风量显著下降说明 (\Gamma) 值合理如果成本涨了但方案安全性提升有限说明 (\Gamma) 过大了可以适当回调。3. 两阶段鲁棒优化调度模型详解3.1 第一阶段决策变量与日前成本两阶段鲁棒模型里第一阶段一般是日前决策对应微网运行中启停调整慢、需要提前确定的变量。常见的包括燃气轮机等可控机组的启停状态 (x_{i,t}) 和启动成本。与大电网购售电的日前合约量 (P_{buy,t}^{DA}, P_{sell,t}^{DA})。储能日前计划的充放电状态变量以及是否需要预先占用部分容量作为备用。第一阶段成本 ( \mathbf{c}^T \mathbf{x} ) 主要包括机组启停成本、固定运行成本以及日前购售电成本。这部分不依赖不确定变量的实际实现是在知道实际光伏出力、负荷之前就必须定下来的。需要特别注意的是第一阶段变量里如果包含储能的充放电“状态”0-1变量则 MP主问题会变成混合整数规划求解时间会明显上升。我见过不少代码直接用双层连续变量建模储能即让 SOC 作为连续状态、充放电功率通过上下限约束限制然后让第二阶段去调整充放电功率。这种处理会丢失“同时充放电”的建模精度但对大多数微网规模的问题合理设置互补约束后影响不大计算速度提升却非常明显。3.2 第二阶段决策变量与调整成本第二阶段对应实时运行阶段当不确定量 (\mathbf{u}) 的真实值光伏出力、风机出力、负荷显现后系统在日前计划基础上做调整。第二阶段变量通常包括燃气轮机的实际出力 (P_{g,i,t}^{RT}) 和向上/向下调整量。储能实际充放电功率 (P_{ch,t}^{RT}, P_{dis,t}^{RT})。与大电网实时交换功率的修正量。切负荷量 (P_{curt,t}) 和弃风、弃光量。第二阶段成本是 (\mathbf{d}^T \mathbf{y})包含实时购电修正成本、切负荷惩罚成本、弃风弃光惩罚成本等。切负荷惩罚系数要设置得足够高比如取购电电价的 10 到 20 倍让优化器在还有操作空间时优先调整机组出力和储能而不是直接切负荷。我在自己的代码里把单位切负荷惩罚设为 50 元/kWh 左右正常情况下鲁棒方案的最优解里切负荷量基本为零这样既能体现约束的存在又不会让模型为了省成本频繁触发惩罚。整个两阶段鲁棒问题的目标函数可以写成[ \min_{\mathbf{x}} \left[ \mathbf{c}^T \mathbf{x} \max_{\mathbf{u} \in U} \min_{\mathbf{y} \in F(\mathbf{x},\mathbf{u})} \mathbf{d}^T \mathbf{y} \right] ]内层的 min 对应给定 (\mathbf{x}) 和实际 (\mathbf{u}) 后的经济调度问题中间的 max 负责寻找最坏场景外层的 min 通过调整日前决策让“日前成本最坏场景调整成本”最小。3.3 约束条件与CCG求解思路约束条件主要包括以下几类功率平衡约束各时段电源出力、储能充放电、购售电、切负荷之和要等于负荷。机组出力上下限和爬坡约束。储能 SOC 递推关系以及充放电功率限制。与大电网联络线交换功率限制。切负荷量不能超过该时段负荷需求。对两阶段鲁棒问题直接求解很困难因为存在嵌套的 min-max-min 结构。目前最通用的解法是列与约束生成算法CCGColumn-and-Constraint Generation。核心思路是把原问题分解为主问题MP和子问题SP通过迭代生成关键列和关键约束来逼近最优解。CCG 算法流程如下初始化设定一个初始的不确定场景 (\mathbf{u}^*)比如取预测值生成初始可行解迭代次数 k1。求解主问题 MP它包含所有已生成的关键场景对应的第二阶段变量和约束得到最优解 (\mathbf{x}^*) 和当前下界 LB。将 (\mathbf{x}^) 代入子问题 SP求解内层 min 对偶后的 max 问题得到当前最坏场景下的调整成本和对应 (\mathbf{u}^)同时更新上界 UB。如果 (UB - LB) 小于收敛精度停止否则把新的场景和对应的第二阶段变量加入主问题kk1回到步骤 2。子问题里内层是一个线性规划可以对其取对偶把 max-min 转化为单个 max 问题。这一步是 CCG 实现的关键也是很多代码容易写错的地方。对偶时的不确定变量 (\mathbf{u}) 如果出现在约束右边对偶后它会进入目标函数求解的时候需要特别处理变量的线性项和符号。在我的实现里第 4 步生成的“列”就是指第一阶段变量对应的新一组第二阶段变量每个场景一组而“约束”则是对应这组变量在山此场景下的功率平衡和出力限制。MP 累计的场景越多方案就越接近真实最优。4. Matlab代码实现与关键函数解析4.1 总体代码框架设计Matlab 里实现这套算法我的习惯是分成五个模块。这样结构清楚出了问题也容易定位。参数初始化模块定义系统参数、不确定集合参数、奖惩系数。场景生成模块LHS 或蒙特卡洛生成初始场景并用聚类/关键场景辨别筛选关键场景。主问题建模模块用 Yalmip 定义第一阶段变量、已生成场景对应的第二阶段变量和约束。子问题建模与对偶求解模块给定第一阶段变量求解最坏场景。CCG 主循环与结果输出模块迭代收敛、绘制曲线、输出调度结果。主循环代码框架大致如下% 初始化不确定场景 u_bar u_pred; % 先取预测场景 LB -inf; UB inf; k 0; while (UB - LB) epsilon % 求解主问题得到x_star和LB [x_star, LB_k, sol_MP] solve_MP(u_bar, system); LB LB_k; % 求解子问题得到最坏场景和调整成本 [u_worst, obj_SP] solve_SP(x_star, system); UB min(UB, cost_DA(x_star) obj_SP); % 更新场景集下一轮加入新场景 if (UB - LB) epsilon u_bar [u_bar, u_worst]; k k 1; end end这里有个容易被忽略的点主问题的下界 LB 不是单调递增的但上界 UB 应该保持单调递减。每次迭代后取历史最优 UB 而不是直接取本轮 obj_SP这样收敛过程更稳定。4.2 场景生成与关键场景辨别代码实现场景生成和辨别是实现鲁棒优化的前置环节。这里给一段比较实用的 Matlab 代码框架% 参数设置 NSample 800; % 初始场景数量 T 24; % 调度时段数 % 光伏出力采样Beta分布按小时取不同参数 pv_samples zeros(NSample, T); for t 1:T pv_samples(:, t) betarnd(alpha(t), beta(t), NSample, 1) .* pv_cap(t); end % 负荷采样正态分布 load_samples repmat(load_forecast, NSample, 1) ... randn(NSample, T) .* (load_forecast * sigma_load); % 关键场景辨别先用K-means分簇再收集簇中心和边界场景 [idx, centers] kmeans([pv_samples, load_samples], K, Replicates, 5); key_scenarios centers; % 初始加入簇中心 for k 1:K cluster_idx find(idx k); cluster_data [pv_samples(cluster_idx, :), load_samples(cluster_idx, :)]; [~, far_idx] max(sum((cluster_data - centers(k, :)).^2, 2)); key_scenarios [key_scenarios; cluster_data(far_idx, :)]; end这段代码里 K-means 用到了 Statistic Toolbox如果机器上没有这个工具箱也可以用 Yalmip 聚类或者自写 K-means 替代。我特别说明一下边界场景收集的逻辑——在每个簇内找离簇中心欧氏距离最远的样本这个样本往往就是该簇的“极端代表”。你可能会问为什么不直接对全部场景做 K-means然后只用簇中心呢前面说过中心聚类天然会把极端场景磨平而鲁棒优化的 worst-case 恰恰在边缘所以必须显式保留。实测中加入边界场景后构造出的不确定集合其盒式上下限比只用簇中心要宽 8%~15%这个差距在恶劣天气日可能非常关键。需要注意的是在把光伏和负荷拼成一个矩阵做聚类前最好先分别归一化。因为光伏出力标幺值在 0~1 之间而负荷大到几十上百 kW如果直接拼接距离度量会被负荷数值主导光伏的变化对聚类结果几乎没有影响。归一化可以用 zscore 或 mapminmaxpv_norm zscore(pv_samples); load_norm zscore(load_samples); data_norm [pv_norm, load_norm];4.3 Yalmip建模与CCG迭代实现细节用 Yalmip 建模最直观。先创建主问题中的变量% 第一阶段变量 x_start binvar(nG, T, full); % 机组启停 P_g_DA sdpvar(nG, T, full); % 日前计划出力 P_buy_DA sdpvar(1, T, full); P_sell_DA sdpvar(1, T, full); P_ch_DA sdpvar(1, T, full); P_dis_DA sdpvar(1, T, full); SOC sdpvar(1, T1, full); % 储能SOC第二阶段变量是按场景生成的。每次 CCG 迭代增加一个场景就生成一组新第二阶段变量u_collection u_pred; % 场景集合 n_iter 0; while ... n_iter n_iter 1; % 创建新的第二阶段变量 P_g_RT sdpvar(nG, T, full, save); P_curt sdpvar(1, T, full); % 对每个关键场景定义约束 for s 1:length(u_collection) % 由u_collection(:, s)获得光伏、负荷 % 添加该场景下的功率平衡与出力约束 end % 求解MP ... end这里有一个很重要的 Yalmip 用法不同场景对应的第二阶段变量是相互独立的但如果直接在循环里创建同名变量Yalmip 会认为它们是一个变量。必须在每个循环里用sdpvar(nG, T, full)新建一组变量并通过Constraints [Constraints, ...]累积。很多初学者在这里踩坑导致变量维度出错或约束混乱。子问题的求解更讲究效率。在 CCG 实现中子问题最优解通常是线性规划的顶点因此可以用linprog或 Yalmip 的optimize直接求解然后从对偶乘子中提取最坏场景 (\mathbf{u})。如果子问题里包含 0-1 变量情况会复杂很多因为对偶性被破坏通常只能采用枚举或 Benders 分解的变种。所以在建模时我建议尽量把第二阶段保持为连续线性问题只把启停等少数 0-1 变量留在第一阶段。4.4 求解器选择与版本注意事项Yalmip 本身不求解优化问题它只是一个建模层。主问题如果是混合整数线性规划MILP建议用 Gurobi 或 CPLEX它们在商用求解器里面对 MILP 的表现最稳。如果是纯线性规划也可以用 Sedumi 或 SDPT3但大规模问题下速度有明显差距。Matlab 版本方面只要 R2018b 以上配合 Yalmip 和 Gurobi 的最新版本基本没问题。旧版本 Matlab 也能跑但要注意 Yalmip 和求解器版本兼容性。另外Matlab 2017 之前对sdpvar(...,save)这类扩展形式的支持较弱可能出现奇怪的报错。我自己的开发环境是 Matlab R2021a Yalmip R20210305 Gurobi 9.5运行很稳定。这里只是推荐如果你用其他版本关键是要保证 Yalmip 的optimize接口正常返回求解器的退出码。关于 Gurobi 的安装我建议直接下载官方版本并在 Matlab 中addpath到 Gurobi 的 Matlab 接口目录然后用gurobi_setup完成配置。这类求解器通常有学术授权在校学生申请很方便。安装完成后可以用yalmiptest验证 Yalmip 能否正确调用 Gurobi。5. 实际算例验证与结果解读5.1 算例参数设置为了验证方法我设计了一个小型微网算例含 1 台燃气轮机、光伏 200kW、风机 100kW、储能 200kWh/100kW、峰值负荷 300kW与大电网联络线容量 250kW。调度周期 24 小时间隔 1 小时。参数数值燃气轮机额定功率100 kW燃气轮机最小技术出力20 kW爬坡速率40 kW/h储能容量200 kWh储能最大充放电功率50 kW储能SOC上下限0.1~0.9光伏装机200 kW风机装机100 kW日前购电电价0.6 元/kWh 平均实时购电电价日前电价的 1.5 倍切负荷惩罚50 元/kWh预算参数Γ6全天24时段光伏预测出力曲线按典型夏季晴天设定包含明显的午间高峰。负荷曲线在早、晚各有一个峰值。场景生成中假设光伏预测误差标准差为预测值的 20%负荷误差标准差为预测值的 5%。5.2 确定性调度与鲁棒调度的结果对比我先跑确定性模型把光伏和负荷都固定在预测值再跑两阶段鲁棒模型比较两者的调度方案和运行成本。确定性模型的总成本是 3280 元鲁棒方案是 3890 元成本上升约 18.6%。这个上升幅度并不是“浪费”而是为应对不确定性预留的安全裕量。在不考虑惩罚的情况下成本上升可能让人皱眉。但换个角度考察把两种方案放到 500 个真实误差场景里做蒙特卡洛回放测试确定性方案在 17% 的场景里出现切负荷平均切负荷量 8.6kWh折合惩罚成本约 430 元鲁棒方案在全部场景中切负荷量为零。把惩罚计入后确定性方案的“实际总成本”均值会超过鲁棒方案。这就是鲁棒优化的价值——它在最坏情况下限定了损失对于供电可靠性要求高的微网这个特性极其重要。从储能运用上看确定性方案更“激进”午间光伏大发时储能充满晚间负荷高峰时全力放电几乎没有备用鲁棒方案则会在储能 SOC 上留 10%~20% 的裕量并在傍晚保留一小部分可调机组出力以应对夜间风光出力突降。这些差异从调度曲线上看非常明显。5.3 关键场景辨别算法的有效性我对 800 个初始场景做关键场景辨别聚类数 K 取 6加上边界场景后共得到 12 个关键场景。为了验证效果我做了三组对比做法场景数MP求解时间秒最终成本元直接用全部800场景8004863885只用K-means簇中心6423760关键场景辨别中心边界12653890只用簇中心虽然快但成本偏低原因就是丢失了边缘的恶劣场景实际上是一个“不够鲁棒”的方案。直接全部场景参与虽然结果更接近真实最坏情况但计算时间暴涨到 486 秒有些场景甚至重复约束大量冗余。关键场景辨别方法在求解时间与全场景相当接近的前提下找到了和全场景几乎一致的最终成本 3890 元说明 12 个关键场景足以还原原始场景池的极端边界信息。从迭代次数看全场景方案需要 11 次 CCG 迭代关键场景方案只需要 8 次迭代就收敛。原因是场景越少主问题的混合整数搜索空间越小每次迭代的求解也更快。对于想把这套方法扩展到更大微网系统或者多微网场景的读者这个计算加速的优势会成倍放大。6. 常见问题与调试经验6.1 CCG收敛慢、不收敛怎么办这类问题我遇到得最多尤其是刚把模型写通之后迭代几十轮还不停。先排查几个原因第一收敛容差设置太紧。工程优化用 1e-3 就足够不必追求 1e-6后者会让迭代次数成倍增加。第二数值尺度问题。如果变量里既有 0.1~100 kW 的功率量又有 0.1~0.9 的 SOC 标幺量求解器内部数值平衡可能崩溃。建议把所有功率量统一到 kW、成本统一到元或者把所有变量做归一化处理。Yalmip 本身对量纲不够敏感要靠建模的人自己把控。第三子问题没有真正求解到最优。CCG 要求每个子问题精确求解不能容忍较大的对偶间隙。如果你是调用的 linprog 且缺省了容差参数可能出现“伪最优”导致上界不更新或震荡。我一般会显式设置求解器参数options sdpsettings(solver, gurobi, gurobi.MIPGap, 1e-4, ... gurobi.FeasibilityTol, 1e-6, gurobi.OptimalityTol, 1e-6);6.2 关键场景聚类K怎么取值K 值太小关键场景太少不确定集合会失真K 值太大计算代价上升。常规做法是结合轮廓系数和肘部法则。轮廓系数的 Matlab 实现不复杂核心是对每个样本计算簇内平均距离和最近邻簇平均距离之比值越接近 1 说明聚类越好。在 K4~10 的范围内画出轮廓系数曲线选系数开始下降的“肘部”位置附近。我自己的算例里 K5 或 6 比较合适这时轮廓系数接近 0.65聚类结构较清晰。另一个更贴鲁棒需求的判断方法看关键场景下 U 集合的上下限包络是否已经涵盖了原始场景池 95% 以上的边界。如果某些时间段的最坏值明显偏离原始最大值说明该时段的关键场景没选中需要增大 K 或者调整距离权重把重点放在负荷峰值和新能源出力低谷时段。6.3 Yalmip建模中的高频错误我在给身边同学看代码时发现几个反复出现的问题这里集中列一下。第一变量维度不匹配。创建P_g_RT sdpvar(nG, T, full)后在循环里对P_g_RT(i, t)赋值需要确保循环索引不越界。更隐蔽的问题是在多个场景循环里使用同一个P_g_RT变量名导致约束错误耦合。一定在每次迭代里新建变量并且用names参数区分场景Yalmip 才把它们看成一族独立变量。第二约束里直接写max(0, P_buy - P_sell)这类非线性表达式。Yalmip 对这种非凸表达式支持很差甚至可能静默生成错误模型。正确做法是用辅助变量和额外的线性约束来替换比如引入购电状态 0-1 变量或者用互补约束。很多情况下微网与大电网的交互可以用单个功率变量 (P_{grid})正为购电、负为售电用上下限约束限制即可不必非要用两个非负变量。第三松弛变量的设置。第二阶段建模时切负荷和弃电的惩罚项对收敛速度和最终成本影响很大。惩罚系数太小优化器会“依赖”切负荷来降低成本导致方案在回放测试里频繁切负荷太大又把问题的目标函数数值搞偏大求解器数值容差问题就会被放大。建议惩罚量级设为日前电价的 20~30 倍并在调试时观察最优解中切负荷是否接近零。6.4 参数调试经验速查关于未知数多、不好调参的读者我列一个直接能用的经验清单不确定集合差范围光伏、风机出力偏差设为预测值的 15%~25%负荷设为 3%~8%超出这个范围会导致方案过保守。预算参数 (\Gamma)第一天可以取 T/3 到 T/2再根据成本-安全性曲线做微调。如果关注单时段极端天气可以加入时段权重将 (\Gamma) 分摊到特定时段。储能SOC上下限留 10% 左右的死区不要用 0~100%否则在鲁棒最坏场景下SOC可能卡到边界导致无解。CCG 最大迭代次数设为 20 到 30 次足够很多问题 10 次以内就收敛了超过 30 次基本说明模型有错误或数值异常。我每次跑新系统都会先把本文的流程走一遍确定性基线 → 场景生成 → 关键场景辨别 → 两阶段鲁棒 → 结果回放。这个过程看起来多花时间但能精确区分“模型问题”和“求解器问题”。最后再分享一个在实际操作中很有用的细节。我用两阶段鲁棒模型做完调度方案后不甘心只拿一张报表又把生成的日前计划拿到 Simulink 的微网仿真环境里做了动态回放。结果发现一个有意思的现象鲁棒方案虽然日前成本高但在实时滚动调度里调整次数少很多储能SOC变化也更平滑这跟仿真里光伏短时波动带来的冲击完全吻合。这让我对这个模型更有信心了——它不只是数学上漂亮实际运行也确实更稳当。如果你要在这个方向继续深入我觉得有两个扩展点特别值得试一是把关键场景辨别和分布鲁棒优化DRCC结合用数据驱动的不确定集合代替手动的盒式集合可以进一步降低保守度二是把第一阶段里的机组启停改成更精细的机组组合约束结合爬坡和最小启停时间会让方案在真实系统里更可落地。这套方法再往上叠加碳交易、需求响应都是现成的路子了。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进