我复现这个“并/离网风光互补制氢合成氨系统容量-调度优化”项目时,最大的感触是:把CPLEX调通只是刚开始,真正花时间的地方在于把风力发电、光伏出力、电解槽制氢、氢储罐缓冲、氨合成装置连续运行这些物理环节,统一翻译成一个能让CPLEX高效求解的混合整数线性模型。标题里的“复现”两个字,恰恰意味着论文里不会给你写好的Matlab代码,你手里只有几张资源曲线、一张设备参数表、十几个约束公式。我在这篇里会把我复现时的建模思路、Matlab调用CPLEX的代码骨架、以及来回踩过的坑都整理出来,目标是你拿到自己的风资源数据和光伏数据之后,能照着搭出同样一个可运行、可解释的双层容量-调度优化框架。
这个项目适合谁读?我的判断是三类人最适合:第一类是正在复现可再生能源制氢类论文的研究生,尤其是遇到“容量和调度耦合”“并网离网对比”这类问题的新手;第二类是从事氢能或风电光伏项目前期规划、想做经济性测算的工程师;第三类是已经会用Matlab但还没有把CPLEX接到自己的优化模型里、希望看一个完整调用链路的技术人员。我不打算讲太多可再生能源的常识,而是直接聚焦到“这个优化是怎么建模的、怎么用CPLEX解的、哪里容易出错”这三件事上。
1. 项目整体:从风光到氨的物理链条与它背后的优化问题
1.1 系统的四个关键环节:风、光、氢、氨
先把这个系统的物理链条捋清楚。风电场和光伏电站发电之后,电能优先送入电解槽,电解水得到氢气。氢气可以先存进储氢罐,这是一个缓冲环节,因为风电和光伏出力在小时级尺度上有很大的随机波动,而下游氨合成装置恰恰希望原材料供给尽量稳定。氨合成装置把电解产生的氢气与空分装置提供的氮气在高温高压下合成氨,产物是液氨或者气氨。
以完整的制氢合成氨流程作为优化背景,涉及到的设备一般至少包括:风电机组、光伏阵列、电解槽(碱性或PEM)、氢气储罐、空分装置、氨合成塔。部分论文中还会加入蓄电池或压缩机组,但我们就先用最精简的流程来理解,后面扩展也容易。
这套系统的核心特征在于“跨能流”和“跨时间尺度”。电能是瞬时的,发出来那一刻就要被消耗或者被丢弃,所以在离网场景下,每一小时都必须满足电力平衡。氢气是有存储缓冲的,白天光伏出力大时多制氢存起来,夜里用储存的氢继续维持氨合成。氨生产也是连续性的,从系统运行经济性来讲,不希望氨合成装置频繁启停,因为频繁启停不仅损失催化剂寿命,还会带来额外的能耗。这些特性最终都会变成优化模型里的约束条件。
1.2 “容量”和“调度”到底分别优化什么
标题里提到的“容量-调度优化”很容易被理解成“先定容量,再做调度”,但我在实际复现中发现,这篇论文的建模重点其实是两者的联立。
容量层解决的是规划问题:风电场要装多大、光伏要装多大、电解槽额定功率是多少、储氢罐容量多大、氨合成装置规模多大。这些变量的量纲是MW、MWh、kg或t/d。容量变量决定整个项目的固定投资成本,所以目标函数里资本成本的量级比较大。
调度层解决的是运行问题:给定一组容量之后,在一天24小时或者一年8760个小时的时间序列上,每小时我应该让风电多发还是弃掉一部分、从电网买电还是自己发电、电解槽在哪个功率水平运行、储氢罐是充放还是保持不动、氨合成装置产率是否调节。调度变量通常都是小时级甚至15分钟级的时间序列变量,目标里对应的是运行成本和收益。
这两种决策天然耦合在一起,因为调度变量的可行域会受到容量变量的限制。比如电解槽额定功率越大,调度时能够消纳的风电就越多,弃风率可能降低,但它每小时运行时的单位成本、以及购买电解槽的成本也在提高。如果在仿真的过程中只做一个单层优化,会得到一个过于乐观或者过于保守的结果。正确做法是通过一个统一的目标函数来权衡容量投资和长时间运行的效果,同时优化两类变量。这个技术细节是整个复现项目的关键点。
1.3 并网与离网:两种场景对模型约束的影响
论文里以“并网”和“离网”两种系统的对比来开展,我的理解是,这两个场景下模型结构与难度区别很大。
并网系统允许与公共电网交换功率。优化时需要考虑分时电价,比如夜间电价低,就可以优先从电网购电来制氢,既降低了制氢成本,又避免了白白扩大风电场容量。但并网通常不允许同一根母线上同时买电和卖电,这意味着需要引入整数变量区分购电/售电状态,模型从线性规划变成混合整数线性规划,求解复杂度和前一种情况完全不同。
离网系统完全靠自产新能源供给,没有大电网作为后盾。这时候电力平衡约束从“柔性可交易”变成“刚性自平衡”,每一小时都必须有充足的风光出力和存储缓冲来满足电解槽、空分等负荷的用电。在设备容量和调度计划上,它对储氢罐容量的依赖会显著增加。我复现下来还发现,离网系统的可行域经常非常狭窄,稍有不慎就会在某个时段出现不可行解。
所以在构建模型时,我倾向于把主模型搭成“可切换模式”的形式,加一个模式开关,通过修改边界条件来分别模拟并网和离网,而不是维护两套代码。
2. 容量-调度耦合模型的建模细节与关键参数
2.1 目标函数:用年化费用统一规划与运行
很多刚接触这种优化模型的人都会问一个问题:容量变量是长期的,调度变量是逐时的,两个量纲不一样,怎么放到一个目标函数里?
我的做法和大多数文献一致,采用“年化成本”的方式。先把所有一次性投资按寿命年限做等年值化,再与一年的运行费用相加。等年值化公式很简单:
$$ A = C_{\text{总}} \cdot \frac{r(1+r)^n}{(1+r)^n - 1} $$
这里的(C_{\text{总}})是初始单位投资,(r)是贴现率,(n)是设备寿命。乘出来的结果叫资本回收系数,它把一次性投资拆到每一年。
% 年化系数计算示例 r = 0.08; % 年贴现率 n_wind = 20; % 风电场寿命 crf_wind = r * (1+r)^n_wind / ((1+r)^n_wind - 1); annual_wind_inv = crf_wind * unit_cost_wind * W_cap; % W_cap单位kW这样目标函数可以写成:
最小化: sum(设备年化投资费用) + sum(年运行维护费用) + sum(每年外购电费用) + sum(每年弃电惩罚,如果有的话) - sum(售电或售氨收益,如果有的话)在并网场景里,外购电费是根据分时电价计算的,每小时的购电功率乘电价,再累加。离网场景中没有外购电这一项,同时为了确保供电可靠性,一般会在目标里加入“负荷失供惩罚”或者“弃电惩罚”,这样模型就不会随便跳出一些极端但不符合工程直觉的解。
2.2 电力平衡约束:从“可以买电”到“必须自平衡”
对于并网场景,调度层每小时要满足:
P_wind(t) + P_pv(t) + P_buy(t) = P_el(t) + P_airsep(t) + P_sell(t) + P_curtail(t)离网场景则是这个等式的简化版,把(P_{\text{buy}})和(P_{\text{sell}})去掉,变成:
P_wind(t) + P_pv(t) = P_el(t) + P_airsep(t) + P_curtail(t)如果你希望通过并网时的购售电价差来决定购电还是售电,还需要给(P_{\text{buy}})和(P_{\text{sell}})之间加一个互斥条件。常用的方式是引入二元变量(y_{\text{buy}}(t)):
P_buy(t) <= M * y_buy(t) P_sell(t) <= M * (1 - y_buy(t))这里(M)是一个足够大的常数。不过提醒一句,(M)别取得过大,否则CPLEX的数学规划松弛质量会变差,求解时间大幅上升。我通常会让M取该线路或设备最大允许功率的1.1倍,这样既保证能放开约束,又不至于破坏分支定界的下界。
2.3 电解槽与制氢环节的建模
电解槽是最核心的可调负荷。它有一个很实用的特性:在相当宽负荷区间内,电耗量和产氢量近似可以按线性关系近似处理。虽然严格意义上电解槽效率会随电流密度变化,但许多容量优化论文里会用一个等效电耗率,比如(5.0\sim5.6\text{kWh/Nm}^3)来建模。
为了统一量纲,我建议把产氢量用质量单位“kg”而不是体积单位“Nm³”放在模型里。因为储氢罐的约束、氨合成装置的氢耗约束都用质量表示更方便。折算大致为:1 kg氢气约等于11.2 Nm³(标准状态下),所以如果设备电耗是5.6 kWh/Nm³,那么相当于约62.7 kWh/kg。
电解槽的建模约束:
m_H2_prod(t) = P_el(t) / W_el_per_kg % 每小时产氢量,kg 0 <= P_el(t) <= P_el_cap如果原论文要求电解槽不能长期在低负荷运行,还要加一条最小运行负荷约束:
P_el(t) >= min_load * P_el_cap * y_el_on(t) P_el(t) <= P_el_cap * y_el_on(t)这个约束是典型的“二进制变量+容量变量”耦合例子。当把容量(P_{\text{el_cap}})也作为优化变量时,模型就出现了容量变量乘以二元变量的双线性项。我在复现类似模型时确实被这个卡过很久。解决办法有两个方向:一个是用大M法把双线性项线性化;另一个更常用的办法是先把容量决策从小时变量中分离出来,在多层迭代框架里处理。我后面会专门讲这两种建模策略的取舍。
2.4 储氢罐的动态方程与周期接续约束
储氢罐的作用相当于电路中的电容,它缓冲氢气的产消不平衡。它的动态约束是一个离散时间状态方程:
S_H2(t + 1) = S_H2(t) + m_H2_prod(t) - m_H2_to_ammonia(t) - m_H2_vent(t) S_min <= S_H2(t) <= S_max其中(S_{\min})一般取最大容量的一定比例,比如5%,作为安全余量;(S_{\max})就是储氢罐容量变量。
很多第一次复现这类模型的朋友会漏掉周期接续约束,也就是一个调度时间域结束后的储氢量要等于初始储氢量,或者至少要满足结束时的水平不低于一个给定值。如果不约束这个,CPLEX会钻空子,把最后一个时段的储氢全部用光或者全部排空,造成结果失真。我在模型中一般加这样的约束:
S_H2(T + 1) >= S_H2(1) S_H2(T + 1) <= S_H2(1)如果使用的是典型日而不是连续一年的数据,则更常见的是让每个典型日首末储氢水平相同。不过这样的话必须注意典型日之间的跨日传递问题,有时候需要用“多个典型日串联,共享同一个初始状态”的做法来避免过度乐观的逐日独立优化结果。
2.5 氨合成环节:化学计量比与最小连续运行约束
合成氨反应的化学计量式是:
[ N_2 + 3H_2 \rightleftharpoons 2NH_3 ]
按质量计算,每生产1吨氨大约需要0.176吨氢气。这个比例几乎在任何复用Haber-Bosch工艺的系统里都不能随便偏离,因为它是化学反应的严格计量约束。
调度层里需要约束:
m_H2_to_ammonia(t) = lambda_NH3 * m_NH3(t) 0 <= m_NH3(t) <= R_NH3_cap如果追求更真实的运行约束,还应对氨合成装置设置最小生产负荷和爬坡速率限制:
m_NH3(t) >= L_min * R_NH3_cap * y_NH3_on(t) m_NH3(t) <= R_NH3_cap * y_NH3_on(t) |m_NH3(t) - m_NH3(t-1)| <= ramp_rate * R_NH3_cap空分装置的耗电也不容忽视。通常氮气制备会有少量电力消耗,估算时可简化为每吨氨耗电0.2~0.3 MWh,具体与空分规模有关。我没有把它当成主体负荷,但它在离网场景中可能占电解槽耗电的5%左右,在精确的电力平衡计算里不能随手省略。
2.6 可再生能源出力数据:从气象时序到模型系数
风光资源数据是模型的外部输入,通常给的是逐小时的风速和辐照度、温度数据。在实际编码中,我会先把它们转成“单位容量出力序列”,也就是假设风电场额定容量是1 MW时,每个小时能发多少电。这样容量变量只是把这段单位时序乘以实际容量,不会引入非线性:
% 单位容量出力序列: pu_w(t), pu_pv(t) % 实际出力 = W_cap * pu_w(t) 和 PV_cap * pu_pv(t)这一步骤非常重要。因为很多新手直接把风速曲线乘一个功率曲线常数后作为固定发电量代入,那就无法把容量作为决策变量了。我一再强调,资源数据必须处理成“每单位装机容量”的出力序列,才能让容量变量成为模型的直接决策变量。
3. 用Matlab调用CPLEX的代码实现细节
3.1 为什么推荐YALMIP来搭桥
Matlab本身没有内置CPLEX接口需要单独配置,有几种方法。第一种是直接用IBM提供的Cplex类,比如Cplex('filename', 'model.lp')或cplexmilp函数;第二种是使用第三方建模工具箱YALMIP,先写好符号优化变量和约束,再在求解时指定solver='cplex',由YALMIP自动转换成CPLEX的输入格式。第二种方法对于科研复现来说更友好,因为模型可读性强,后期改约束很快,不容易弄错维度。
我实际使用的是YALMIP加CPLEX的组合。YALMIP可以用sdpvar定义连续变量,用binvar或integer定义整数变量,写约束和写普通数学公式一样,阅读起来非常直观。
% 决策变量:容量变量 W_cap = sdpvar(1,1); PV_cap = sdpvar(1,1); El_cap = sdpvar(1,1); H2_cap = sdpvar(1,1); NH3_cap = sdpvar(1,1); % 调度变量:时间序列 P_buy = sdpvar(T,1); P_sell = sdpvar(T,1); P_el = sdpvar(T,1); S_H2 = sdpvar(T+1,1);这里sdpvar的维度和规模都直接对应小时数T。如果T是8760小时,变量数量会很大;如果采用典型日,比如12个典型日、每个典型日24小时,那么T实际是288,规模就很容易控制在CPLEX能秒解的范围内。
3.2 典型日选取与权重设计
在复现这类容量优化时,全年8760小时数据直接代入会让MILP规模超出合理范围,尤其当还有大量二元变量时。所以我通常采用两部走:先用kmeans算法从全年气象和历史负荷序列中聚类出若干个典型日,再把每个典型日的目标函数乘以该类别在全年的天数权重。
% 假设 RawData 是 N*48矩阵,N为全年天数,前24列为风速单位出力,后24列为光伏单位出力 [idx, C, sumd, D] = kmeans(RawData, K); % K=12 或16 weights = histcounts(idx, 1:K+1);聚类后,模型中的目标函数就变成:
sum(weights(k) * sum_24(运行成本(k)))同时所有时间序列变量都只在一个典型日内部流动,典型日之间通过权重在目标函数中汇总。这个处理在容量规划里已经被广泛接受,因为设备年利用小时数的整体趋势是靠气象聚类覆盖的,而不是用某一个代表日硬扛全年。
3.3 单层MILP实现容量-调度联立优化的思路
标题里的“容量-调度优化”,有很多论文会选择把容量作为决策变量,和调度变量放在同一个混合整数规划中一次性求解。这个思路的可实现性很高,只要满足两个条件:第一,所有约束关于容量变量都是线性的;第二,目标函数中不存在容量变量乘以调度变量的非线性项。
我特别想说清楚一个坑。比如电解槽约束要求“如果运行,功率要大于最小技术出力”,写作:
P_el(t) >= min_ratio * El_cap * y_el_on(t)这个约束看起来很简单,但实际上同时包含连续变量(El_cap)和二元变量(y_el_on(t)),二者相乘是一个双线性项,不是标准LP/MILP约束。如果直接扔给YALMIP,它会调内置非线性求解器或者报错,而不会走CPLEX的MILP分支。
解决办法是引入辅助连续变量(z(t)),代表“电解槽在t时刻使用的额定容量比例”:
z(t) = y_el_on(t) * El_cap然后再通过大M法线性化。但这样的线性化会增加变量和约束数量,而且如果容量变量已经有多个设备、多个时段,会非常麻烦。所以我在复现中更常见的做法是,将这类约束改写为:
P_el(t) >= min_ratio * El_cap - M * (1 - y_el_on(t))这实际上是同一个约束的“松约束”形式,含义是当(y_{on}=1)时,(P_{el}(t))必须高于最小负荷;当(y_{on}=0)时,右边减去一个很大的M,约束就不起作用。这样写之后,CPLEX可以当作标准MILP处理。
3.4 约束编写与优化求解的完整骨架
为了让你能直接上手,我把一个简化版的Matlab/YALMIP骨架整理出来,这个骨架包含并网场景的大多数核心约束。实际使用时,你可能还需要扩展设备数量、典型日权重、收益项等。
%% 参数 T = 24; % 每小时调度,一个典型日24点 pu_w = 随机或实际生成...; % 1×24, 单位容量出力 pu_pv = 随机或实际生成...; %% 容量变量 W_cap = sdpvar(1,1); PV_cap = sdpvar(1,1); El_cap = sdpvar(1,1); H2_cap = sdpvar(1,1); NH3_cap = sdpvar(1,1); %% 调度变量 P_el = sdpvar(T,1); P_wind = sdpvar(T,1); P_pv = sdpvar(T,1); P_buy = sdpvar(T,1); P_sell = sdpvar(T,1); P_cur = sdpvar(T,1); % 弃电功率 S_h2 = sdpvar(T+1,1); H2_prod = sdpvar(T,1); % 每小时产氢 kg H2_to_nh3 = sdpvar(T,1); m_nh3 = sdpvar(T,1); % 每小时产氨 kg y_switch = binvar(T,1); % 1 表示购电,0表示售电 %% 约束 C = []; % 风光出力上限 C = [C, P_wind == W_cap .* pu_w]; % 实际风=容量*单位出力 C = [C, P_pv == PV_cap .* pu_pv]; C = [C, P_wind + P_pv + P_buy == P_el + P_airsep_const + P_sell + P_cur]; % 购售互斥 M = 5000; % 根据系统最大功率取 C = [C, P_buy <= M .* y_switch]; C = [C, P_sell <= M .* (1 - y_switch)]; % 电解槽 C = [C, H2_prod == P_el / wh_per_kg]; % wh_per_kg约62 C = [C, P_el <= El_cap]; % 储氢 C = [C, S_h2(2:T+1) == S_h2(1:T) + H2_prod(1:T) - H2_to_nh3(1:T)]; C = [C, S_h2 >= 0.05 * H2_cap]; C = [C, S_h2 <= H2_cap]; C = [C, S_h2(1) == S_h2(T+1)]; % 氨合成 C = [C, H2_to_nh3 == 0.176 * m_nh3]; C = [C, m_nh3 <= NH3_cap]; %% 目标:年化投资+运行成本 obj = crf_w * cost_w * W_cap + crf_pv * cost_pv * PV_cap ... + crf_el * cost_el * El_cap + crf_h2 * cost_h2 * H2_cap ... + crf_nh3 * cost_nh3 * NH3_cap; obj = obj + 365 * sum(P_buy .* elec_price); obj = obj + 365 * 1e-3 * sum(P_cur); % 弃电惩罚 %% 求解 ops = sdpsettings('solver', 'cplex', 'verbose', 2); ops.cplex.mip.tolerances.mipgap = 1e-4; ops.cplex.timelimit = 600; sol = optimize(C, obj, ops);这里的P_airsep_const是我为了简化处理设置的空分固定负荷;真实建模时也可以把空分功率和氨产量挂钩,变成变量。由于是示意代码,很多单位换算和边界条件还需要按你实际数据修正。
3.5 CPLEX求解器参数的使用心得
CPLEX作为商业求解器,它的核心优势在于大规模MILP的分支定界速度和稳定的数值处理能力。实际使用中,我一般最关心mipgap、timelimit、nodefile和threads这几个参数。
ops.cplex.mip.tolerances.mipgap = 5e-3; % 0.5%的gap足够工程使用 ops.cplex.threads = 8; % 并行线程数 ops.cplex.mip.strategy.file = 2; % 节点文件压缩策略,内存紧张时开如果发现在一个小时内没有达到理想gap,不用太焦虑。先看CPLEX日志里的MIP gap是百分之几。如果gap已经低于1%,但模型规模导致时间很长,此时强行追求gap到0.01%意义并不大,因为容量规划的最优解在1%的gap范围内已经完全能支撑工程决策。
如果模型报“infeasible”而你又很难排查是哪条约束导致不可行,我个人建议在目标函数中引入一个松弛变量,比如失电负荷,或者先固定容量变量成一组合理值再做纯调度优化,这样可以把可行性问题定位到是容量不足还是调度约束有矛盾。
4. 复现并网/离网系统时的实操经验与常见问题
4.1 结果怎么看:容量结果与调度结果互相校验
跑出结果后,不要急着截图写报告。至少要做两层校验。
第一层校验是总量平衡。以100 MW风电场、50 MW电解槽为例,假设风电场年利用小时数为2200小时,年产电量就是2.2亿kWh。如果电解槽制氢单位电耗为5 kWh/Nm³,即约56 kWh/kgH2,那么一年最多可产氢约3928吨。如果按每吨氨消耗0.176吨氢折算,这些氢大约对应年产氨2.23万吨。如果你的优化结果给出年产氨3万吨,那就说明风电利用小时数或者电解槽容量设置出了偏差,需要回头检查输入条件。
第二层校验是趋势合理性。离网场景中,储氢罐的储量曲线应该呈现昼夜波动并在连续运行中逐步回补,不可能出现连续几天放空后又在几个小时内充满的情况。并网场景的购电行为应与电价波动相对应,夜间电价低谷时应该有更多购电制氢,白天高电价时段则会减少购电甚至售电。如果完全反着来,大概率是目标函数中价格的符号写反了,或者单位不匹配。
下面这张表是我复现时常用的一个快速自检表,能帮你快速定位问题方向。
| 检查项 | 常见异常 | 可能原因 |
|---|---|---|
| 购电量与电价序列 | 高电价时段反而大量购电 | 目标函数价格符号错误或功率单位弄错 |
| 储氢罐水平 | 长期处于下边界或末时刻被清空 | 缺少周期接续约束或储氢容量边界取错 |
| 弃风/弃光量 | 离网系统弃电量为0且无失负荷 | 可能遗漏了切负荷变量,系统被过度约束 |
| 产氨量 | 高于化学计量上限 | 氢耗系数取错或单位没统一 |
| CPLEX求解时间 | 几小时不收敛 | 整数变量过多或M值太大 |
| 不可行问题 | solver直接返回infeasible | 容量变量太小或缺少松弛变量 |
4.2 离网系统里的“假可行”问题
离网系统的运行目标强调了供电可靠性。如果在目标函数里既没有失电惩罚项,又没有设置可靠性约束,优化器很可能会选择极小的容量配置,让系统在全年绝大部分时段都无法供电,但目标函数还是最优。这显然不符合期望。
所以在离网模型中,我建议用一个“失电负荷”或“氢产量不足”的连续变量来兜底,对它的单位惩罚量定到非常高的水平,让优化器只在极个别极端的时段选择牺牲可靠性。对于容量规划,还可以进一步限制系统的“自供电率”或“氢供应不足率”不超过某一阈值,例如利用年氢缺量比例不超过2%,写成累积约束:
sum(m_H2_shortage(t)) <= 0.02 * sum(m_H2_demand(t))这类约束能有效抑制“过度牺牲可靠性换经济性”的倾向,也更符合实际情况。
4.3 并网波动的整数变量爆炸与应对
并网模式下,如果每小时的购售状态都用二元变量表示,全年的二元变量数就是8760个。虽然对CPLEX而言不算极端,但一旦加上每个电解槽的启停变量、氨合成装置的最小运行状态变量,变量规模会迅速膨胀。再加上典型日方法会引入周期性耦合,模型一般会在数分钟内出现解,然而如果想分出较优的MIP解常需要较长时间。
为了控制模型复杂度,我的实践经验有三个方向:一是把时间分辨率从1小时拉长到2小时或3小时,在某些阶段先粗仿一遍找趋势,再选取关键区间加密分辨率;二是合并同型号电解槽,用聚合机组模型替代逐台模型,这样启停变量从N台减少为机群级别;三是用滚动时域的思路,先固定容量结果,再做更细时间粒度的调度校验,不必把全年8760个小时都塞进同一个MILP。
4.4 关于“复现”这件事:代码不是唯一重点
最后回到“复现”这个词。我在复现多篇同类型论文后有个感受,代码层面的复现和论文结论层面的复现并不是一回事。跑通代码只能说明你把公式写对了,但论文里的结果往往基于特定的风资源数据、光伏数据、设备价格和贴现率,而论文并不会把所有数据都放到附录里。当你换了一套数据以后,最优容量比例、氨生产成本很可能发生较大变化。因此复现的价值不在于得到一个和论文一模一样的电价曲线或容量结果,而在于你能解释为什么论文在某个电价水平下选择“多装风电解槽少装氢罐”,这才是容量-调度优化问题的核心规律。
实际项目中让我印象最深的一次调整是,同一套风力数据和光伏数据,只把贴现率从10%降到6%,最优的光伏容量占比就明显上升,因为高贴现率会惩罚初期投资大的设备,而光伏单位投资虽然低,但利用小时数低,在高贴现率下并不占优。这也再次说明容量与调度联立优化的必要性:如果不做全日历逐时模拟,单靠经验公式很可能会得到错误的装机比例。
从工程落地的角度看,这类优化结果通常还要再做一次极端气象年的稳健性校验。资源曲线如果换成枯风年或少光年,系统是否仍然可靠、成本是否会大幅上升,这些都是容量规划中必须回答的问题。而这些扩展大多不需要改变模型结构,只要把输入数据替换后再求解一遍即可,这也是用YALMIP加CPLEX这种可读性较强的方式建模的额外好处。
如果你正在复现类似的“风光互补制氢合成氨”容量-调度优化,我个人建议先从离网场景入手,因为它约束清晰、可行域窄,方便你快速发现逻辑错误;等离网模型稳定性、自平衡和储氢缓冲的逻辑都通过验证后,再切换到并网场景加入购售电变量。这样的递进式复现路线,会比一上来就同时处理两种模式省去大量检查时间。