简介:面向电力系统调度、钢铁生产管理和能源优化研究人员,这份资源针对钢铁厂热轧负荷参与电网调峰的实际需求,提供了可调能力评估方法的完整复现方案。内容涵盖多产线热轧负荷建模、电力调峰市场与生产用能优化的关联分析,以及兼顾调峰容量与成本约束的综合评估方法。资源以Python代码和详细注释展示了热轧负荷模型类、调峰市场模型类、基线生产计划生成与优化求解等关键环节,并配有仿真结果分析,便于读者理解模型背后的物理与经济含义。包内为1个docx文档,共54KB,文件虽精简但代码模块完整,可直接修改参数适配不同产线规模与市场机制。目前已有51人学习下载,可结合日内与日前两类调峰场景,分析热轧负荷可调容量、成本及其影响因素,为制定合理调峰策略和优化生产计划提供决策支持。
1. 调峰为什么要评估热轧可调能力:一个被多数方案跳过的“可行性边界”问题
钢铁工业面向调峰需求的热轧负荷可调能力评估,这两年已经从“锦上添花”变成电力现货省份的刚需。热轧不像电弧炉,不能说停就停——中间坯在辊道上等久了会温降,精轧可能咬不进去;加热炉重新蓄热要烧掉更多煤气,轧辊与检修窗口也有固定节奏。所以“能调多少”不是拍脑袋出来的数,而是给定轧制计划、检修窗口、电气容量和市场结算规则之后,算出来的可行域。本文适合在钢厂做能源调度、在负荷聚合商侧做需求响应建模、或者研究工业负荷柔性调节的人。我会把评估指标、多产线协同优化的模型与代码、市场机制下的收益测算这条链路拆开讲,并落在一个可复现的最小案例上。
2. 热轧负荷可调能力的基础指标:先知道“能调多少”与数据怎么算出来的
2.1 热轧负荷的时间结构:为什么不能用单点功率代表可调能力
热轧产线的用电结构大体分三块:加热炉的连续稳定负荷,粗轧、精轧主传动电机的冲击性负荷,以及液压、润滑、水处理等辅机负荷。其中冲击性负荷随出钢节奏周期性波动,精轧机组咬钢瞬间的尖峰功率可以比轧制间隙高出两到三倍。只看某一时刻的功率表,你会误以为“现在负荷低,说明可调能力强”,实际上那条线可能正在等温、在开过钢间隙,下一步咬钢马上要把功率拉满。真正的可调能力必须放在一个时段里看,比如 15 分钟、1 小时或一个轧制单元,先界定时间段,再谈下调量。
这也解释了为什么可调能力评估不能直接用历史最大需量减最小需量来算。热轧负荷的波动本来就是生产节律造成的,不是可调节造成的。评估的目标是从“生产计划的自然波动”里分离出“不破坏工艺约束的可控偏移”,这个偏移才是能拿去申报调峰的资源量。如果直接把峰谷差当作能力,到了执行日电网要求稳定压减负荷时,你这边出钢节奏一变,负荷自己就会反弹,考核就会翻车。
2.2 四个核心评估指标:可调深度、爬坡速率、持时、恢复能力
实际做评估时,我一般先给每条产线建立四个指标,而不是一上来就建立优化模型。四个指标分别回答四个问题:能压多少、压得多快、能压多久、压完能不能回来。
| 指标 | 含义 | 常用单位 | 典型量级(按 1580mm 热连轧线参考) |
|---|---|---|---|
| 可调深度 | 稳定运行状态下,相对基线负荷能下调的功率 | MW | 8~20 |
| 爬坡速率 | 单位时间内功率的变化能力 | MW/min | 3~8 |
| 最短持时 | 维持低负荷水平的最短或最长时段 | min | 20~90,受中间坯待轧温降限制 |
| 恢复能力 | 降负荷后回到基线所需时间与超调幅度 | min / MW | 30~60,恢复期易超调 20% 以上 |
可调深度受限于轧线最低稳定运行功率,不能低于主传动和辅机维持生产所需的下限;爬坡速率由主电机变频能力和加热炉供热惯性共同决定;最短持时是热轧特有的限制——粗轧出来的中间坯在辊道或保温坑里等太久,温度低于精轧入口要求就得判废回炉;恢复能力则与加热炉蓄热状态直接相关,炉温已经被压低之后,重新升温需要时间,恢复时段容易出现负荷超调。这四个指标可以先用 SCADA 历史数据粗算,再通过现场试验修正。
2.3 数据来源与预处理:SCADA、PCS、L2 与时间戳对齐
模型的数据来源通常是三套系统:SCADA 或者 PI 数据库存电气量,轧线 L2 过程计算机存轧制事件(咬钢、抛钢、换辊、待轧),能源管理 PCS 存班次产量与吨钢能耗。常见做法是把 L2 里的轧制事件时间戳和 SCADA 的功率曲线对齐,按事件把负荷段切成“正常轧制、待轧保温、停机检修、故障中断”几类,然后只拿正常轧制段做基线统计。如果不做这一步,混合了待轧段的功率曲线会把可调深度的基准算低,后面市场结算时基线会吃亏。
还有一个坑是采样间隔不一致。SCADA 一般 1 到 5 秒一个点,电能质量表可能只有 15 分钟冻结值,而 L2 事件是秒级触发的。计算爬坡速率时如果用 15 分钟粒度的数据,峰谷皆被平滑,算出 MW/min 会明显偏低;混合不同来源又会算出虚高值。我一般先把所有序列重采样到同一时间基准,比如统一到 1 分钟或 5 分钟,再在重采样前对 SCADA 原始秒级数据做 3 倍中值滤波,去掉功率采集的毛刺。数据干净了,后面的评估指标才有意义。
3. 多产线协同优化模型:把调峰目标写成可求解的 MILP 并跑通最小案例
3.1 为什么要多线协同:电气容量、轧制节奏与物流耦合
单独评估一条产线的可调能力不难,难在厂里有几条轧线共用同一变电站母线、同一加热炉区、同一连铸出坯节奏。电气侧,母线容量有限,一条线降负荷腾出的容量,另一条线可能正在启动辅机,净削减量不是简单相加。物流侧,热轧的原料来自炼钢-连铸,连铸出坯节奏基本固定,轧线若为调峰刻意停太久,下游中间库胀库,上游连铸可能被迫降速,成本会从电费转移到钢坯库存和铸机状态上。
多产线协同优化的意义,就是把这些耦合约束放进同一个模型里,统一回答“三条线各自压多少、谁停谁不停、各自在什么时候恢复”。这比逐条线评估后人工加总要可靠得多,也能直接输出可供电网调度的逐时负荷计划曲线。
3.2 模型目标与约束:可调能力不是“想调多少调多少”
我习惯把问题建模成混合整数线性规划(MILP)。决策变量包括每条产线每个时段的有功功率、运行/停机状态、启动/停机动作;目标函数在调峰补偿收入与产量损失、风险成本之间取平衡;约束包括功率上下限、爬坡速率、最小连续运行/停机时长、轧制计划硬性必产时段,以及电网调度给出的总负荷削减要求。
目标函数的核心是:调峰时段内削减电量带来的补偿收益,减去所有时段减产带来的机会成本和重新加热多耗的能源成本。注意这里“削减电量”是基于基线负荷算的,基线取多少,直接影响模型会不会为了拿补偿而故意夸大削减量。约束里最关键的是最小运行/停机时长——如果模型忽略它,最优解会告诉你在调峰时段内把某条线停 10 分钟再启动,这在热轧现场根本不可能实现,因为停机一次至少要完成排空辊道、保温或回炉处理。
3.3 用 Python + PuLP 跑通 3 条产线的调峰指令最小案例
下面这个案例假设一个厂有 3 条热轧产线,24 小时一个调度日,电价型补偿单价在调峰时段生效,电网要求 14:00 到 17:00 总负荷较基线削减至少 30 MW。先定义参数与决策变量:
import pulp lines = [0, 1, 2] # 三条轧线编号 hours = range(24) # 1 小时一个时段,共 24 个时段 T = 24 # ---- 每条产线的电气与工艺参数(按实际标定值修改) ---- P_min = {0: 18, 1: 22, 2: 15} # MW,产线最低稳定运行负荷 P_max = {0: 45, 1: 55, 2: 40} # MW,产线最大可带负荷 R_up = {0: 6, 1: 8, 2: 5} # MW/时段,升负荷能力 R_dn = {0: 8, 1: 9, 2: 6} # MW/时段,降负荷能力 UT = {0: 4, 1: 6, 2: 3} # 小时,最小连续运行时长 DT = {0: 3, 1: 4, 2: 2} # 小时,最小连续停机时长 P0 = {0: 40, 1: 50, 2: 35} # MW,调度周期初始负荷 y0 = {0: 1, 1: 1, 2: 1} # 1 表示初始在运 # 基线负荷:不参与调峰时的正常预测值 P_base = {0: 40, 1: 52, 2: 36} # 调峰时段与补偿参数 peak = set(range(14, 17)) # 14:00-17:00 price = {t: 400 for t in peak} # 元/MWh,补偿单价 loss_cost = 120 # 元/MWh,减产对应的机会成本系数 # 电网要求:调峰时段每条产线总和比基线低 30 MW cut_req = 30 # MW # 决策变量 P = {(i, t): pulp.LpVariable(f"P_{i}_{t}", 0, None) for i in lines for t in hours} # 产线负荷 y = {(i, t): pulp.LpVariable(f"y_{i}_{t}", cat="Binary") for i in lines for t in hours} # 运行状态 start = {(i, t): pulp.LpVariable(f"start_{i}_{t}", cat="Binary") for i in lines for t in hours} stop = {(i, t): pulp.LpVariable(f"stop_{i}_{t}", cat="Binary") for i in lines for t in hours} prob = pulp.LpProblem("hot_rolling_DDR", pulp.LpMaximize)这里把所有变量先创建为字典,方便后续循环加约束。P 的上下界在约束里通过运行状态 y 来限制,而不直接写 0 到 P_max,因为停机时功率必须为 0,运行时才允许在 P_min 和 P_max 之间。start 和 stop 用于表达状态切换,它们是二值变量,用来实现最小连续运行和停机时长约束。
下面建立约束与目标函数:
# 目标:调峰补偿最大化,同时扣减产机会成本 prob += ( pulp.lpSum(price[t] * (P_base[i] - P[i, t]) for i in lines for t in peak) - pulp.lpSum(loss_cost * P[i, t] for i in lines for t in hours) ) # ---- 约束一:负荷与运行状态匹配 ---- for i in lines: for t in hours: # 停机时负荷为 0,运行时负荷必须在 P_min 与 P_max 之间 prob += P[i, t] >= P_min[i] * y[i, t] prob += P[i, t] <= P_max[i] * y[i, t] # 爬坡约束,启停机瞬间允许负荷跳变 prev = P0[i] if t == 0 else P[i, t - 1] prob += P[i, t] - prev <= R_up[i] * y[i, t] + P_max[i] * (1 - y[i, t]) prob += prev - P[i, t] <= R_dn[i] * y[i, t] + P_max[i] * (1 - y[i, t]) # ---- 约束二:状态切换与启停动作一致 ---- for i in lines: for t in hours: prev_y = y0[i] if t == 0 else y[i, t - 1] prob += y[i, t] - prev_y == start[i, t] - stop[i, t] # ---- 约束三:最小连续运行时长 ---- for i in lines: for t in hours: prob += (pulp.lpSum(start[i, k] for k in range(max(0, t - UT[i] + 1), t + 1)) <= y[i, t]) # ---- 约束四:最小连续停机时长 ---- for i in lines: for t in hours: prob += (pulp.lpSum(stop[i, k] for k in range(max(0, t - DT[i] + 1), t + 1)) <= 1 - y[i, t]) # ---- 约束五:2 号线 10-12 点必产(轧制计划硬约束)---- for t in range(10, 12): prob += y[2, t] == 1 # ---- 约束六:调峰时段总负荷削减不低于 30 MW ---- for t in peak: baseline_total = sum(P_base[i] for i in lines) prob += pulp.lpSum(P[i, t] for i in lines) <= baseline_total - cut_req # 求解 solver = pulp.PULP_CBC_CMD(timeLimit=60, msg=0) prob.solve(solver) print("求解状态:", pulp.LpStatus[prob.status]) for t in hours: if t in peak: total_load = sum(pulp.value(P[i, t]) for i in lines) base_total = sum(P_base[i] for i in lines) print(f"时段 {t}: 基线 {base_total:.1f} MW,计划 {total_load:.1f} MW," f"削减 {base_total - total_load:.1f} MW")求解状态为 Optimal 时,输出每一调峰时段的削减量。我在实际项目里还会加一条约束:调峰时段结束后 2 小时内总负荷不得超过基线的 110%,用来抑制恢复阶段的反弹。你直接运行这段代码,CBC 求解器会在几秒内给出结果;把三条产线的参数替换成自己厂的实测值,模型结构可以原样保留。
3.4 结果解读与参数敏感性:调参时先看哪几项
这个小时粒度模型跑出来的结果,通常会优先让最小连续停机时长最短的 2 号线承担更多削减,因为它的启停惩罚最小。如果你发现某条线在结果里始终没有被压负荷,不要急着怀疑求解器,先查这条线的 UT 和 DT 是否设置过大,或者 P_min 是否接近 P_base。P_min 接近 P_base 意味着这条线本来就压不动,可调深度趋近于零,这是评估阶段就该发现的事情。
爬坡速率在小时粒度模型里偏松弛,因为 R_up 和 R_dn 的单位是 MW/时段,而现场描述爬坡能力通常用 MW/min。你要是把现场标定的 5 MW/min 直接填进这个模型,相当于默认一小时能爬 300 MW,约束形同虚设。正确做法是做分钟级模型,或者把时段改成 15 分钟并把爬坡能力折算进去。我在项目里一般先用小时模型做能力上限估算,再用 15 分钟粒度模型验证可执行性,两个模型结果差距超过 15% 时,先回头检查数据采集问题,而不是急着调权重。
4. 市场机制分析:补偿、基线、考核如何决定“评估出来的能力”值不值钱
4.1 三类常见调峰市场机制及热轧的适配性
可调能力评估出来后,下一步是判断它能在哪种市场机制里换成收益。不同机制的提前通知时间、结算方式和考核严格度差异很大,直接影响你应该按多大力度申报能力。
| 机制类型 | 通知提前量 | 结算方式 | 热轧适用性 |
|---|---|---|---|
| 邀约型需求响应 | 提前 4~24 小时 | 按实际削减电量 × 补偿单价结算 | 最合适,热轧可以倒排计划 |
| 现货市场价差套利 | 日内滚动更新 | 按实时电价与自身负荷曲线结算 | 适合可调深度大、恢复快的产线 |
| 容量/备用补偿 | 长期合同签订 | 按申报可调容量按月补偿 | 需先通过可调能力评估和现场试验 |
热轧不适合做秒级或分钟级的自动功率调节,因为主传动轧制节奏没法被电网 AGC 信号直接控制。邀约型机制给了足够的准备时间,可以把调峰时段嵌入轧制计划里,比如把换辊、待轧保温安排到调峰时段。现货市场价差套利对数据要求更高,需要预测次日电价,再把预测电价输入到第 3 章的模型里,把目标函数从补偿收益改成电费成本最小化。
4.2 基线负荷的算法直接影响结算结果
所有市场机制里,基线负荷都是结算的锚点。常见做法是取调峰日前 5 个正常生产日同一时段的负荷平均值。基线偏高,削减量虚高,申报能力被放大,但也会带来月结算时的考核风险。热轧负荷本身有周期性波动,如果这 5 天里恰好包含某条线待轧时间偏长的班次,基线会比典型生产日低,你会少算能力;反过来,如果 5 天里都是满负荷快节奏轧制,基线偏高,执行日稍有不达标就会被考核。
我的建议是不要把基线当成一个固定数,而是把基线的不确定性直接放入收益测算里做敏感性分析。计算收益时分别用“乐观基线(高值)、常规基线、保守基线(低值)”三个版本,考察净收益是否仍然为正。如果只有乐观基线为正,这个调峰能力从市场角度看是不值得投入的。
4.3 收益/风险测算:一个能直接落地的 Python 函数
配合市场结算,我写一个净收益测算函数,输入是计划负荷曲线、基线曲线、补偿单价和风险参数,输出削减电量、收入、风险成本和净收益。核心逻辑是把“电费收益”和“生产风险成本”分开算,避免只看补偿单价就上头。
def ddr_pnl(load_mwh, base_mwh, price_per_mwh, risk_params): """ load_mwh: 调峰时段逐时实际计划负荷,单位 MWh base_mwh: 调峰时段逐时基线负荷,单位 MWh price_per_mwh: 削减电量补偿单价,单位 元/MWh risk_params: 风险成本参数字典 """ cut_mwh = sum(max(0.0, b - l) for b, l in zip(base_mwh, load_mwh)) income = cut_mwh * price_per_mwh # 补偿收入 # 风险成本:多条线重新加热多耗电 + 轧辊损耗 + 废钢风险 reheating_cost = sum(load_mwh) * risk_params.get("reheat_cost_per_mwh", 0) roll_wear_cost = cut_mwh * risk_params.get("roll_wear_per_mwh", 0) scrap_cost = cut_mwh * risk_params.get("scrap_loss_per_mwh", 0) risk_cost = reheating_cost + roll_wear_cost + scrap_cost return { "cut_mwh": cut_mwh, "income": income, "risk_cost": risk_cost, "net": income - risk_cost, } # 示例:某调峰日 15:00-16:00 的测算 load = [38.0, 36.0, 18.0] base = [45.0, 52.0, 36.0] risk = { "reheat_cost_per_mwh": 40, "roll_wear_per_mwh": 15, "scrap_loss_per_mwh": 25, } result = ddr_pnl(load, base, price_per_mwh=400, risk_params=risk) print(result)参数说明:cut_mwh 是削减电量,只看正削减、不把负荷高于基线的时段拿来抵充;reheat_cost_per_mwh 模拟加热炉重新升温的额外电耗和煤气成本,由吨钢能耗增量折算;roll_wear_per_mwh 指因节奏变化导致轧辊热疲劳增加的换辊成本;scrap_loss_per_mwh 是中间坯温降判废的风险折算。这些参数没有统一标准,我的做法是把过去三个月的生产异常报表和能源报表对齐,倒推出单位削减量的平均损失成本,再换成元/MWh 填入函数。第一次评估没有历史数据时,可以先用保守值压住风险,等积累几个调峰日后回头校准。
5. 热轧调峰落地避坑:五个翻车场景与排查方法
5.1 检修窗口与磨辊计划冲突:有评估能力却没有“可调窗口”
现象:模型算出可调深度很充足,但到申报时发现厂里正在检修或者预告有换辊计划,实际能参与调峰的时段没几天。原因:可调能力评估用了全时段数据,没有把检修计划、轧辊周期和定修窗口作为硬约束带入。解决:在模型里把检修和换辊时段直接固定为不可调,或者用“可调窗口”作为筛选条件,先剔除不可用时段,再评估能力。省下来的精力比现场临时改计划少得多。
5.2 中间坯温降被低估:轧线降负荷后恢复不了的现场原因
现象:调峰时段压负荷执行很顺利,但指令结束前提前 30 分钟恢复时,精轧入口温度不够,连续两块中间坯判废。原因:待轧保温能力只覆盖了正常轧制节奏下的短时等待,低估了长时段降功率造成的温降累积。解决:把最短持时从“理论等待时间”改成“按中间坯厚度、钢种、保温坑状态实测的温度窗口”,并在优化模型里增加“恢复前必须完成重新加热”的逻辑约束。血泪经验:宁可削减量报小一点,也比判废一块坯划算。
5.3 加热炉蓄热滞后:恢复时段的负荷反弹超出爬坡上限
现象:调峰结束时总负荷迅速冲到基线以上,实测爬坡速率远超模型设定,被调度记录为“恢复期超调”。原因:模型里的恢复约束只限制了自己定义的爬坡速率,而加热炉的蓄热补偿是滞后的,炉温下降后重新升温会同时拉高风机和燃料系统负荷,这部分没有进入建模范围。解决:在恢复时段约束里额外加一个“负荷不得高于基线 110%”的软约束,并在现场试验中实测不同停机时长后的蓄热补偿曲线,把它作为模型参数。
5.4 SCADA 与 L2 时间戳不对齐:算出的爬坡速率虚高
现象:现场做试验时,SCADA 显示功率 3 分钟内降了 12 MW,评估报告把爬坡能力写成 4 MW/min,第二次同样操作却只降了 6 MW。原因:SCADA 时间戳和 L2 的轧制事件时间戳没有对齐,咬钢/抛钢事件对应的功率片段被错位拼接,把两次降功率叠加到了一起。解决:先做事件对齐,以 L2 的抛钢信号为基准,截取每个轧制间隙的功率曲线,再逐段计算爬坡速率。对齐后如果数值依然波动大,要检查是不是有液压泵加载等辅助负荷叠加在主传动事件上。
5.5 基线含检修日:月结时“多算的收益”反被考核
现象:月度结算发现削减电量被电网侧复核为负偏差,不仅补偿没了,还交了考核费用。原因:申报时用了包含检修日的 5 天平均基线,检修日负荷低,抬高了基线,导致实际削减量达不到结算门槛;更隐蔽的是这 5 天里的待轧保温时段也被算进正常生产平均值里。解决:基线选取前先逐日打标签,剔除检修、故障和待轧占比过高的日期,再用剩余正常生产日重算。如果市场规则允许,优先选用分时段基线,减少单日异常对平均值的影响。这件事没有捷径,只能靠日常把生产事件标注做好。
6. 用历史数据做一场“伪实时”回测:验证模型值得被信任
6.1 回测框架:历史负荷、价格曲线与模拟调峰指令
评估模型能不能被信任,最有效的方法不是看拟合误差,而是做一场“伪实时”回测——把历史某一天的负荷、价格曲线当作当时已经发生的数据,重新跑一遍优化与结算流程,再与实际执行结果对比。由于你已经知道那天发生了什么,回测暴露的偏差比任何指标都直观。
def pseudo_real_time_backtest(days, forecast_base, price_curve, solve_model): logs = [] for day in days: plan_load = solve_model(day) # 用第 3 章模型求解逐时计划 actual_load = simulate_execution(plan_load) # 模拟实际执行扰动后的负荷 base = forecast_base[day] cut_mwh = sum(max(0.0, b - l) for b, l in zip(base, actual_load)) logs.append({ "day": day, "plan_cut": sum(max(0.0, b - p) for b, p in zip(base, plan_load)), "actual_cut": cut_mwh, "income": cut_mwh * price_curve[day], }) return logssimulate_execution 是自己写的一个执行偏差模拟函数,实际扰动来源包括中间坯温降、加热炉恢复延迟、辅机负荷随机波动。回测时要注意不能用同一份数据既是输入又是验证,通常取前一周数据定参、后一周数据回测。如果 plan_cut 和 actual_cut 系统性偏差超过 10%,先查是不是调度指令与轧制计划存在时间差,再回头检查模型里是否缺少了恢复时段约束。
6.2 从回测到滚动调度:下一步能做的事与我的习惯
回测通过后,可以把第 3 章的 MILP 模型接入日常滚动调度:每天上午拿到次日电价预测和检修计划,中午跑一次 24 小时优化,把建议的逐时负荷曲线发给生产计划员,人工确认后申报。这个过程不需要改造任何现场设备,只把评估模型变成一个“负荷计划建议工具”。再往后可以考虑把加热炉热状态加进模型,用分段线性函数表达炉温与电耗的关系,让热轧负荷曲线更接近实际物理过程。
我自己在项目里的习惯是连续两周跑同一组历史数据,每天都把模型输出与次日实际执行曲线叠在一起看,先确认偏差是趋势性的还是时段性的,再决定要不要调参数。趋势性偏差优先查基线,时段性偏差优先查启停约束和爬坡参数。这套流程从指标评估到回测走通一遍大概两周,之后每次谈调峰补偿,至少手里有一份经得起复核的数字,而不是靠感觉报能力。希望帮到你。
本文还有配套的精品资源,点击获取