ARTICLE DETAIL

资讯详情

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

基于Copula的风光出力相关性场景生成与Matlab实现

基于Copula的风光出力相关性场景生成与Matlab实现 1. 项目概述与核心思路先说结论这个项目做的是在已知历史数据的基础上用Copula把风光出力的相关性结构抽出来再按这个结构生成出足够多的、带相关性的随机场景用于后续电力系统的规划、调度或可靠性评估。做电力系统的人都知道风电和光伏的出力都不是孤零零存在的。同一片区域里风来了往往云也来了光伏出力往下掉或者某些地区白天风大、晚上风小跟光伏正好错开。如果你在做随机优化时把风电和光伏当作两个独立变量各自单独采样再组合得到的结果会严重偏离真实情况——要么低估了系统调峰压力要么高估了可用发电量。这类问题在含高比例新能源的电网分析里非常致命。场景生成解决的就是这个问题。它不追求精确预测某一天的出力曲线而是要生成一大批像真的会发生的风、光出力组合覆盖各种相关性和极端情况。Copula连接函数在其中扮演的角色就是专门负责把单变量的概率分布和多变量之间的相关性拆开建模边缘分布管每一维自己的形态Copula只管多维之间的黏性。这套方案在Matlab里实现非常顺因为Matlab自带的Statistics and Machine Learning Toolbox里有copulafit、copularnd、copulacdf等一连串现成函数不需要自己从头写数值优化。适合的读者包括做电力系统随机调度的研究生、做新能源场站并网评估的工程师、做负荷预测相关课题的科研人员以及想快速上手Copula但还没找到完整落地路径的朋友。2. 为什么是Copula相关性建模的核心原理2.1 用一个生活类比理解Copula想象你有一男一女两个朋友分别问你他们各自的身高和体重。如果你只关心某人身高超过175cm的概率和某人体重超过75kg的概率那每个指标单独建一个分布就够了。但如果你想知道身高超过175cm的人同时体重超过75kg的概率有多大这就牵扯到两个指标之间的相关性了。Copula做的事情就是把身高分布和体重分布之间的那种联动关系单独拎出来描述。你可以先用任意方式拟合好各自边缘分布再用Copula把相关性黏合上去。类比到风光场景里风速边缘分布用Weibull辐照度边缘分布用Beta或混合Gamma两者之间的联动关系用Copula表达。2.2 Sklar定理与联合分布分解Copula的理论根基是Sklar定理。它说对于任意一个二维联合分布函数F(x, y)一定存在一个Copula函数C(u, v)使得F(x, y) C(F_x(x), F_y(y))其中F_x、F_y是边缘分布函数u F_x(x)、v F_y(y)都是[0,1]区间上的均匀分布变量。这是一个非常强的分解你完全不需要假设风、光服从同一个分布族也不需要强行用多元正态去套非线性关系只需要分别找好各自的边缘分布再用Copula把坐标空间变换到标准均匀空间后再加相关性。这个拆分的工程价值非常明显。传统方法是假设风、光联合服从二维正态分布——这在多数条件下是错的因为风的实测分布往往右偏光的实测分布往往有大量接近零的无光照时段强行用二维正态拟合出来的联合密度会非常离谱。用Copula后你可以说风速边缘用Weibull辐照度边缘用混合分布两者之间的相关性结构用Clayton Copula每一步都是独立可检验的。2.3 常用Copula家族怎么选Matlab的copulafit支持Gaussian、t、Clayton、Frank、Gumbel五种常用Copula。差异在于对尾部相关性的刻画能力Copula类型尾相关特征适用风光场景的直观理解Gaussian上下尾均无强相关适合相关性比较温和、极端情况也相对独立的场景t上下尾对称相关适合既有高风也有低风、且极端高值和极端低值都容易同时发生的场景Clayton下尾相关强适合出力都很低的情况容易同时出现——比如阴天无风Gumbel上尾相关强适合出力都很高的情况容易同时出现——比如大风且晴朗Frank尾部相关弱适合中间区域相关、但两端趋于独立的场景以我的经验实际做风光联合出力时最常用的是Gaussian和Clayton。Gaussian胜在参数少、稳定适合没有强极端相关性的场景Clayton能把无风又无光的尾部联合事件刻画的更真实在计算电力系统失负荷风险时特别有价值。3. 边缘分布建模与参数拟合3.1 风速边缘分布风电出力和风速直接相关。忽略具体风机功率曲线细节的话最常用的风速分布是两参数或三参数Weibull分布概率密度为f(v) (k/λ) × (v/λ)^(k-1) × exp(-(v/λ)^k)其中k是形状参数λ是尺度参数。实测风速一旦取对数变换再做线性拟合就能快速估计出k和λ不需要迭代。但更稳妥的做法还是用Matlab的fitdist% 假设 wind_hist 是历史风速时间序列m/s pd_wind fitdist(wind_hist, Weibull); k_wind pd_wind.B; lambda_wind pd_wind.A;这里有一点容易踩坑fitdist里Weibull返回的A、B分别对应尺度参数λ和形状参数k跟教科书符号正好反过来我第一次用的时候就没注意导致后面采样结果形态完全不对。3.2 辐照度边缘分布光伏出力建模比风速麻烦因为辐照度有大量夜间零值和白天高峰数据呈现零膨胀 峰值集中的双重特征。直接套一个Beta分布往往拟合得很差。简单处理方式有两种一种是把历史辐照度转换到0-1标幺值以后先做零值比例统计再用仅针对非零时段的Beta分布描述有光照部分的形态light_nonzero light_hist(light_hist 0); light_pu light_nonzero / max(light_hist); pd_light fitdist(light_pu, Beta); p_zero sum(light_hist 0) / numel(light_hist);另一种是直接用核密度估计ksdensity做非参数边缘分布好处是形态拟合能力强坏处是超出历史范围时推断能力弱。我个人在工程复现时更倾向于混合模型但既然项目标题强调的是Copula的相关性结构边缘分布这块选取合理且能跑通的方案即可不必在模型复杂度上过多纠缠。3.3 用empirical CDF转换到均匀空间在拟合Copula之前必须先把原始出力数据转成[0,1]区间上的均匀分布样本。最通用方法是用经验CDFEФu_wind ksdensity(wind_hist, wind_hist, function, cdf); u_light ksdensity(light_hist, light_hist, function, cdf);或者用排序秩转换u_wind (tiedrank(wind_hist) - 0.5) / length(wind_hist); u_light (tiedrank(light_hist) - 0.5) / length(light_hist);注意这里不能用fitdist得到的理论CDF直接替换原始数据因为Copula要求输入本身必须是均匀分布样本。如果手动转换或估计方法不对会导致后面参数估计出现系统偏差。这是我在几次复现里确认过的关键点。4. Matlab完整实现流程与代码拆解4.1 相关性结构拟合拿到均匀化数据矩阵U [u_wind, u_light]之后下一步就是调用copulafit。你既可以直接指定Copula类型也可以让Matlab自动选% 方式一指定Gaussian Copula rho_gau copulafit(Gaussian, U); % 方式二指定Clayton Copula返回的alpha是Clayton参数 alpha_clay copulafit(Clayton, U); % 方式三遍历多种Copula按AIC/BIC选最优 families {Gaussian, t, Clayton, Frank, Gumbel}; aic_vals zeros(1, length(families)); for i 1:length(families) try [~, aic_vals(i)] copulafit(families{i}, U); catch aic_vals(i) inf; end end [~, best_idx] min(aic_vals); best_family families{best_idx};这里copulafit的第二个输出在部分Matlab版本中返回的是对数似然值在较新版本中会直接返回AIC或BIC需要看版本里function signature。建议提前help copulafit确认。另外要强调默认copulafit拟合Gaussian和t Copula时估计的是线性相关矩阵Rho不是Kendall秩相关。如果你拿历史数据的Kendall tau直接往里填会出现量纲不匹配。正确理解是Matlab内部会做转换你提供均匀分布样本U即可不需要自己算tau再换算。4.2 场景采样与逆变换拟合完成后用copularnd生成指定数目的均匀空间样本N 1000; if strcmp(best_family, Gaussian) U_sim copularnd(Gaussian, rho_gau, N); elseif strcmp(best_family, Clayton) U_sim copularnd(Clayton, alpha_clay, N); end此时得到的U_sim每一列都是[0,1]均匀分布但列之间带有Copula相关性。要恢复成风、光出力实际值的场景需要把每一列做逆变换% 先用之前的风速边缘分布做逆CDF wind_scene wblinv(U_sim(:,1), lambda_wind, k_wind); % 光伏需要同时处理零值比例 light_scene zeros(N, 1); nonzero_idx U_sim(:,2) (1 - p_zero); light_pu_sim U_sim(nonzero_idx, 2); % 将条件概率重新映射到Beta分布分位数区间 light_pu_sim (light_pu_sim - (1 - p_zero)) / p_zero; light_scene(nonzero_idx) betainv(light_pu_sim, pd_light.A, pd_light.B) * max(light_hist);这段代码里有一个非常关键的逻辑如果你的光伏边缘分布是零膨胀Beta混合那么直接betainv(U_sim(:,2))会把一部分生成场景变成负值或者错误映射。正确做法是在零值概率p_zero附近做条件分布变换U大于1-p_zero时认为有光照然后把这一段均匀变量重新映射到[0,1]区间再做Beta逆变换。风速就没这么复杂直接用wblinv就能得到符合实际量纲的风速值。如果你还想进一步得到风光出力而不是风速/辐照度那就再加一条风机功率曲线和光伏转换效率模型% 假如风机额定功率P_wr切入风速v_in额定风速v_r切出风速v_out P_wind zeros(N, 1); P_wind(wind_scene v_in wind_scene v_r) ... P_wr .* (wind_scene(wind_scene v_in wind_scene v_r) - v_in) / (v_r - v_in); P_wind(wind_scene v_r wind_scene v_out) P_wr; % 光伏功率按标幺辐照度近似线性 P_light light_scene .* P_pv_rated;4.3 完整主干代码框架下面给一个经过我整理、可以直接改路径运行的主干框架。数据格式假设是两列历史数据第一列风速m/s第二列辐照度kW/m²或标幺值%% 加载历史数据并做清洗 data load(wind_light_history.mat); wind_hist data.wind_hist; light_hist data.light_hist; %% 去除异常值和NaN valid isfinite(wind_hist) isfinite(light_hist); wind_hist wind_hist(valid); light_hist light_hist(valid); %% 边缘分布拟合 pd_wind fitdist(wind_hist, Weibull); k_wind pd_wind.B; lambda_wind pd_wind.A; light_hist_pu light_hist / max(light_hist); light_nonzero light_hist_pu(light_hist_pu 0); pd_light fitdist(light_nonzero, Beta); p_zero sum(light_hist_pu 0) / numel(light_hist_pu); %% 转均匀分布 u_wind (tiedrank(wind_hist) - 0.5) / length(wind_hist); u_light (tiedrank(light_hist) - 0.5) / length(light_hist); U [u_wind, u_light]; %% 拟合Copula [rho, ~] copulafit(Gaussian, U); %% 生成场景 N_scene 2000; U_sim copularnd(Gaussian, rho, N_scene); %% 逆变换得到风、光出力 wind_scene wblinv(U_sim(:,1), lambda_wind, k_wind); light_scene zeros(N_scene,1); has_light U_sim(:,2) (1 - p_zero); u_light_cond (U_sim(has_light,2) - (1 - p_zero)) / p_zero; light_scene(has_light) ... betainv(u_light_cond, pd_light.A, pd_light.B) * max(light_hist); %% 场景矩阵整理 scene_matrix [wind_scene, light_scene];这个框架里噪声最小、跑出来形态也最稳定。实际项目中可以再增减但核心链条原始数据 → 边缘分布 → 均匀化 → copulafit → copularnd → 逆变换一定不能乱。4.4 代码中几个不容易注意到的细节第一拟合边缘分布和拟合Copula的数据区间要一致。有人喜欢用归一化之后的数据拟合边缘分布又用原始数据做秩转换两边区间对不上生成的场景不是偏大就是偏小。第二copularnd的随机数流可控性。在做论文实验或重复性测试时建议在采样前设置rng固定随机种子rng(42); U_sim copularnd(Gaussian, rho, N_scene);这样才能保证每次运行结果可复现。很多审稿人或者项目验收方会要求这个这在工程报告里是一票式要求。第三Matlab在计算Copula时默认输入必须是严格在(0,1)开区间内的均匀样本如果某个值正好等于0或1例如最大风速排序后按公式算出来刚好等于1可能报错或计算出NaN。所以上面用(tiedrank - 0.5)/length来避免端点问题不要大意。5. 结果可视化与场景有效性检验5.1 生成场景的散点形态对比代码跑完后第一个要看的图是历史数据转均匀分布后的散点和生成场景在均匀空间的散点在形态上是否一致figure; subplot(1,2,1); plot(u_wind, u_light, .); xlabel(u_wind); ylabel(u_light); title(历史数据均匀空间); subplot(1,2,2); plot(U_sim(:,1), U_sim(:,2), .); xlabel(u_wind); ylabel(u_light); title(Copula生成场景);如果两组散点的密集方向、聚拢程度对得上说明Copula结构复现成功。这个图不要只看corr系数还要看四角的密集程度若左下角比右上角密说明存在下尾相关性Clayton可能比Gaussian更合适。5.2 用量化指标检验相关性常用指标是Kendall秩相关系数tau和Spearman秩相关系数。分别对历史数据和场景数据计算tau_hist corr(wind_hist, light_hist, Type, Kendall); tau_sim corr(wind_scene, light_scene, Type, Kendall); fprintf(历史Kendall tau %.3f\n, tau_hist); fprintf(场景Kendall tau %.3f\n, tau_sim);如果两者差异在0.05以内基本合格。差异偏大时优先检查边缘分布的逆变换是否准确尤其是光伏那一路的零膨胀处理。5.3 场景缩减从2000个到几十个生成了2000个场景不可能全部扔进优化模型里。业内常用同步回代消除法fast backward reduction或者K-means聚类做场景缩减。我在这里推荐K-means的简化方案[idx, c] kmeans(scene_matrix, 50, Replicates, 10); scene_reduced c; % 每个聚类中心所代表的概率按聚类包含的样本数量计 prob histcounts(idx, 50) / N_scene;缩减后的50个场景配合概率值就可以替代原始历史数据输入到两阶段随机规划或者机会约束规划里计算量小很多同时能保留风光相关性的主要特征。注意K-means用欧氏距离做聚类对于量纲差很大的风、光出力要先分别归一化否则风速的数值范围会主导聚类结果光伏出力维度会被完全忽略。6. 常见踩坑与调试思路6.1 拟合时报错Data must be in the interval这个报错特别常见。原因基本是U矩阵里出现了0或1。前面提过的tiedrank处理可以解决。还有一种隐蔽情况如果原始数据里风速为0的样本特别多排序转换后最小值仍然很小但不会为0可一旦用ksdensity的cdf输出可能会因为核密度带宽导致少量超过[0,1]区间的浮点值进而报错。建议统一用tiedrank方式。6.2 生成的光伏场景出现负值这是光伏逆变换时条件映射写错导致的。常见原因是直接用betainv(U_sim(:,2))而不是加条件变换。记住一个原则数据有零膨胀时[0,1]均匀区间的前面一部分必须留给零值后面一部分才映射到Beta分布。具体划分点就是p_zero。6.3 相关性符号反了有的地区风速与光照是负相关拟合出来的rho或alpha是负值。这本身没问题Gaussian Copula天然支持负相关。但Clayton Copula不支持负相关如果数据本身是负相关却选了Claytoncopulafit可能会给出极小的正参数导致场景相关性接近0完全丢失结构。解决办法是在选族之前先看一眼散点图和tau的正负号正相关可考虑Gaussian、Clayton、Gumbel负相关优先Gaussian或Frank。6.4 不同Matlab版本copulafit输出差异新版Matlab里copulafit(Gaussian, U)会返回rho部分版本还会返回对数似然或者AIC具体取决于是用两个输出还是三个输出调用。建议在代码开头处加一个动态判断避免版本切换后脚本报错try [rho, ~] copulafit(Gaussian, U); catch rho copulafit(Gaussian, U); end6.5 场景数量过多导致优化模型崩溃生成5000个场景直接放进机组组合模型除非机器配置很高否则求解时间会让你怀疑人生。建议生成2000个但缩减到50个并且对概率做归一化保证概率和等于1。缩减必须在场景生成之后做不能在历史数据上直接聚类否则你只是把历史数据重采了一遍没有真正利用Copula外推出来的新场景。7. 项目扩展与个人实践体会实际做项目时我一般不会只停留在风、光两个变量上。负荷、水电、储能出力也都可以接入同一个Copula框架只是维度越高Copula拟合越困难需要转向pair-Copula或者基于R-vine的方法。Matlab自带的copulafit只支持二维和少量三维场景三维以上要额外写vine结构那是后话。另一个经常被忽略的扩展方向是把Copula生成的场景应用于鲁棒优化里做不确定性集合构建。比如从生成的场景中抠出一定置信度的超矩形或椭圆集合作为鲁棒优化的不确定集边界比单纯用历史数据的盒式不确定集更紧、更符合真实相关性。我在实际使用中有几点感受特别深。第一Copula场景生成的质量天花板往往不取决于Copula本身而是取决于边缘分布拟合的质量。边缘分布烂了再好的Copula结构也会输出一批形态怪异的场景。第二不要迷信AIC选出来的最优Copula工程上稳定性远比一点点似然值提升重要。Gaussian Copula的稳健性让我在多个项目中都避免了过度拟合除非数据里很明显存在尾部相关性否则我默认先上Gaussian。第三做场景生成时一定要把评判标准放在最终应用里。如果场景是用于电网风险评估那么左下角低出力联合事件保真度比整体相关性更重要如果用于容量规划平均值和分位数才是关键。场景好看不好看不重要下游任务算得准才算数。这套方法难吗数学原理有一定门槛但借助Matlab的现成工具箱真正难的是把数据清洗、边缘分布、Copula拟合、逆变换和场景缩减串成一条可靠流水线。上面这些代码和踩坑记录基本覆盖了我从零到一跑通全过程遇到的所有关键问题。照着做一遍你也能把这个项目复现出来并且能在自己的数据上动手改成风、光、负荷多变量版本。
返回列表