GLPK/电力市场
本教程包含一组示例,演示了用于市场化调度的竞争性电力市场的特征。虽然澳大利亚国家电力市场 (NEM) 被用作这些示例的参考,但该市场与其他竞争性电力市场具有许多共同特征,可以为更普遍的市场化调度提供有用的见解。
单周期电力调度可以被视为一个流量网络问题 - 每个区域(以及稍后的每条传输线路)由一个节点表示,每个无损链接由一个双向弧表示。这里提供的示例不是作为线性规划构建的。这两种公式在数学上是等效的(尽管首选的算法通常不同)。尽管如此,在研究这些示例时,将网络视图保留在脑海中仍然是有用的。
本示例的目的是演示在一定时间范围内,考虑到燃料成本和爬坡速率,对单个发电机组进行调度,并在整个时间范围内最大化该机组的总利润。
/* Simple single unit dispatch Dr. H J Mackenzie 2010-03-24 */ # set of dispatch intervals set I; # dispatch price is $ param regionalprice {I}; # unit characteristics param unit_max_capacity >= 0; param fuel_cost >= 0; param max_ramp_rate >= 0; param start_dispatch >= 0; # dispatch variables var dispatch {I} >= 0; var ramp {I}, >= - max_ramp_rate, <= max_ramp_rate; var profit {I}; # objective function maximize totalprofit: sum {i in I} profit[i]; # constraints s.t. initial_dispatch: dispatch[0] = start_dispatch; s.t. dispatch_profit {i in I}: profit[i] = dispatch[i] * (regionalprice[i] - fuel_cost); s.t. dispatch_ramp {i in I}: ramp[i] = dispatch[i] - dispatch[if i > 0 then i-1 else 0]; s.t. unit_capacity {i in I}: dispatch[i] <= unit_max_capacity; # solve the problem solve; # output input and determined values printf {i in I} "%d regionalprice = %.1f; dispatch = %.1f; ramp = %.1f; profit = %.1f\n", i, regionalprice[i], dispatch[i], ramp[i], profit[i]; data; param unit_max_capacity := 100; /* MW */ param fuel_cost := 30; /* $/MWh */ param max_ramp_rate := 20; /* max MW up or down */ param start_dispatch := 0; /* present dispatch point MW */ param : I : regionalprice := 0 15 1 17 2 18 3 22 4 55 5 40 6 65 7 10 8 12 9 4 ; end;
该解决方案在大约 10 次迭代中找到,显示了机组在有利可图的运行之前进行的调度,以最大限度地提高整个时间集的总利润,为 2680 美元。不幸的是,在现实世界中,区域价格在调度间隔之前是未知的,因此该方法仅适用于调度发生后或在预调度定价的情况下,隐含假设机组的调度相对于区域价格来说微不足道。
* 10: obj = 2.680000000e+003 infeas = 0.000e+000 (0) OPTIMAL SOLUTION FOUND Time used: 0.0 secs Memory used: 0.1 Mb (135015 bytes) 0 regionalprice = 15.0; dispatch = 0.0; ramp = 0.0; profit = 0.0 1 regionalprice = 17.0; dispatch = 0.0; ramp = 0.0; profit = 0.0 2 regionalprice = 18.0; dispatch = 20.0; ramp = 20.0; profit = -240.0 3 regionalprice = 22.0; dispatch = 40.0; ramp = 20.0; profit = -320.0 4 regionalprice = 55.0; dispatch = 60.0; ramp = 20.0; profit = 1500.0 5 regionalprice = 40.0; dispatch = 80.0; ramp = 20.0; profit = 800.0 6 regionalprice = 65.0; dispatch = 60.0; ramp = -20.0; profit = 2100.0 7 regionalprice = 10.0; dispatch = 40.0; ramp = -20.0; profit = -800.0 8 regionalprice = 12.0; dispatch = 20.0; ramp = -20.0; profit = -360.0 9 regionalprice = 4.0; dispatch = 0.0; ramp = -20.0; profit = 0.0
本示例类似于上一个示例,但引入了市场调度辅助服务的复杂性,以进行系统调节。在真实的澳大利亚 NEM 中,有六种市场调度服务,分别用于 6 秒(R6SEC)、60 秒(R60SEC)、5 分钟(R5MIN)和调节(RREG)的升压,以及 6 秒(L6SEC)、60 秒(L60SEC)、5 分钟(L5MIN)和调节(LREG)的降压。在本示例中,仅考虑单一区域的 RREG 服务,该区域的市场成本最小化。该示例没有考虑有关辅助服务调度的现实世界约束,在这些约束中,不同输出级别可以提供不同数量的服务,在澳大利亚市场中,这些服务是通过为每种辅助服务定义的梯形以及每个机组的物理特性来实现的。
/* Least cost unit dispatch for energy with regulation Dr. H J Mackenzie 2010-03-24 */ # set of units set U; # maximum capacity of the units in MW param capacity {U}; # offer price of the energy in $/MWh param offerprice {U}; # maximum regulation capacity of the units in MW param regcapacity {U}; # regulation offer price of the regulation energy in $/MWh param regofferprice {U}; # market parameters param dispatch_demand >= 0; param regdispatch_demand >= 0; # dispatch variables var dispatch {U} >= 0; var cost {U}; var regdispatch {U} >= 0; var regcost {U}; # objective function minimize market_cost: sum {u in U} (cost[u] + regcost[u]); # constraints s.t. dispatch_cost {u in U}: cost[u] = dispatch[u] * offerprice[u]; s.t. regdispatch_cost {u in U}: regcost[u] = regdispatch[u] * regofferprice[u]; s.t. feasible_dispatch {u in U}: dispatch[u] + regdispatch[u] <= capacity[u]; s.t. feasible_regdispatch {u in U}: regdispatch[u] <= regcapacity[u]; s.t. dispatch_matches_demand: sum {u in U} dispatch[u] = dispatch_demand; s.t. regdispatch_matches_demand: sum {u in U} regdispatch[u] = regdispatch_demand; # solve the problem solve; # output input and determined values printf {u in U} "%d capacity = %.1f; offerprice = %.1f; regcapacity = %.1f; regofferprice = %.1f\n", u, capacity[u], offerprice[u], regcapacity[u], regofferprice[u]; printf {u in U} "%d dispatch = %.1f; cost = %.1f; regdispatch = %.1f; regcost = %.1f;\n", u, dispatch[u], cost[u], regdispatch[u], regcost[u]; data; param dispatch_demand := 45; /* MW */ param regdispatch_demand := 10; /* MW */ param : U : capacity offerprice regcapacity regofferprice:= 1 10 10 5 1 2 10 15 5 7 3 10 20 5 13 4 10 25 5 19 5 10 30 5 25 6 10 35 5 31 7 10 40 5 37 8 10 45 5 43 9 10 50 5 49 10 10 55 5 55 ; end;
结果表明,该区域的(电力)能源调度受到辅助服务提供的影響,因此导致总市场成本高于仅考虑能源的情况。请注意,调节服务的增加导致了调度,如果仅检查能源调度,则不会很明显。
* 13: obj = 1.090000000e+003 infeas = 0.000e+000 (0) OPTIMAL SOLUTION FOUND Time used: 0.0 secs Memory used: 0.1 Mb (137757 bytes) 1 capacity = 10.0; offerprice = 10.0; regcapacity = 5.0; regofferprice = 1.0 2 capacity = 10.0; offerprice = 15.0; regcapacity = 5.0; regofferprice = 7.0 3 capacity = 10.0; offerprice = 20.0; regcapacity = 5.0; regofferprice = 13.0 4 capacity = 10.0; offerprice = 25.0; regcapacity = 5.0; regofferprice = 19.0 5 capacity = 10.0; offerprice = 30.0; regcapacity = 5.0; regofferprice = 25.0 6 capacity = 10.0; offerprice = 35.0; regcapacity = 5.0; regofferprice = 31.0 7 capacity = 10.0; offerprice = 40.0; regcapacity = 5.0; regofferprice = 37.0 8 capacity = 10.0; offerprice = 45.0; regcapacity = 5.0; regofferprice = 43.0 9 capacity = 10.0; offerprice = 50.0; regcapacity = 5.0; regofferprice = 49.0 10 capacity = 10.0; offerprice = 55.0; regcapacity = 5.0; regofferprice = 55.0 1 dispatch = 5.0; cost = 50.0; regdispatch = 5.0; regcost = 5.0; 2 dispatch = 5.0; cost = 75.0; regdispatch = 5.0; regcost = 35.0; 3 dispatch = 10.0; cost = 200.0; regdispatch = 0.0; regcost = 0.0; 4 dispatch = 10.0; cost = 250.0; regdispatch = 0.0; regcost = 0.0; 5 dispatch = 10.0; cost = 300.0; regdispatch = 0.0; regcost = 0.0; 6 dispatch = 5.0; cost = 175.0; regdispatch = 0.0; regcost = 0.0; 7 dispatch = 0.0; cost = 0.0; regdispatch = 0.0; regcost = 0.0; 8 dispatch = 0.0; cost = 0.0; regdispatch = 0.0; regcost = 0.0; 9 dispatch = 0.0; cost = 0.0; regdispatch = 0.0; regcost = 0.0; 10 dispatch = 0.0; cost = 0.0; regdispatch = 0.0; regcost = 0.0;
在本示例中,我们研究了在单个时间段内,每个区域在不同价格区间具有不同的容量以及不同的区域需求的情况下,两个区域的调度。目标函数寻求找到两个区域的最低成本解决方案。对于本示例,假设每个区域之间没有链接损失。在这种情况下,能源是指电力能源。
/* Least cost unit dispatch for energy with two regions Dr. H J Mackenzie HARD software [email protected] 2010-03-25 */ # set of units set U; # maximum capacity of the units in MW for each region a and b param regiona_capacity {U}; param regionb_capacity {U}; # offer price of the energy in $/MWh param offerprice {U}; # market parameters param regiona_dispatch_demand >= 0; param regionb_dispatch_demand >= 0; param ab_link_capacity >= 0; # dispatch variables var regiona_dispatch {U} >= 0; var regionb_dispatch {U} >= 0; var unit_dispatch {U} >= 0; var ab_link_dispatch; /* positive flow is defined as flow from region a to region b */ var cost {U}; # objective function minimize market_cost: sum {u in U} cost[u]; # constraints s.t. dispatch_cost {u in U}: cost[u] = ((regiona_dispatch[u] + ab_link_dispatch) * offerprice[u]) + ((regionb_dispatch[u] - ab_link_dispatch) * offerprice[u]); s.t. feasible_regiona_dispatch {u in U}: regiona_dispatch[u] <= regiona_capacity[u]; s.t. feasible_regionb_dispatch {u in U}: regionb_dispatch[u] <= regionb_capacity[u]; s.t. total_unit_dispatch {u in U}: unit_dispatch[u] = regiona_dispatch[u] + regionb_dispatch[u]; s.t. feasible_ab_link_dispatch: ab_link_dispatch <= ab_link_capacity; s.t. feasible_ba_link_dispatch: -ab_link_dispatch <= ab_link_capacity; s.t. regiona_dispatch_matches_demand: sum {u in U} regiona_dispatch[u] - ab_link_dispatch = regiona_dispatch_demand; s.t. regionb_dispatch_matches_demand: sum {u in U} regionb_dispatch[u] + ab_link_dispatch = regionb_dispatch_demand; # solve the problem solve; # output input and determined values printf {u in U} "%d regiona_capacity = %.1f; regionb_capacity = %.1f; offerprice = %.1f\n", u, regiona_capacity[u], regionb_capacity[u], offerprice[u]; printf {u in U} "%d dispatch = %.1f; ab link dispatch = %.1f; cost = %.1f\n", u, unit_dispatch[u], ab_link_dispatch, cost[u]; data; param ab_link_capacity := 10; /* MW */ param regiona_dispatch_demand := 9; /* MW */ param regionb_dispatch_demand := 22; /* MW */ param : U : regiona_capacity regionb_capacity offerprice:= 1 10 0 10 2 10 0 20 3 10 0 30 4 0 10 15 5 0 10 25 6 0 10 35 ; end;
解决方案显示了每个区域的调度,由于区域 A 的需求较低且发电成本较低,因此链接以满负荷运行。
* 5: obj = 4.800000000e+002 infeas = 0.000e+000 (0) OPTIMAL SOLUTION FOUND Time used: 0.0 secs Memory used: 0.1 Mb (140601 bytes) 1 regiona_capacity = 10.0; regionb_capacity = 0.0; offerprice = 10.0 2 regiona_capacity = 10.0; regionb_capacity = 0.0; offerprice = 20.0 3 regiona_capacity = 10.0; regionb_capacity = 0.0; offerprice = 30.0 4 regiona_capacity = 0.0; regionb_capacity = 10.0; offerprice = 15.0 5 regiona_capacity = 0.0; regionb_capacity = 10.0; offerprice = 25.0 6 regiona_capacity = 0.0; regionb_capacity = 10.0; offerprice = 35.0 1 dispatch = 10.0; ab link dispatch = 10.0; cost = 100.0 2 dispatch = 9.0; ab link dispatch = 10.0; cost = 180.0 3 dispatch = 0.0; ab link dispatch = 10.0; cost = 0.0 4 dispatch = 10.0; ab link dispatch = 10.0; cost = 150.0 5 dispatch = 2.0; ab link dispatch = 10.0; cost = 50.0 6 dispatch = 0.0; ab link dispatch = 10.0; cost = 0.0
传输线路中的损耗是二次方形式,例如 ,因此需要用线性形式近似,以便使用线性规划 (LP) 公式求解。解决这个问题的方法是使用以下公式近似函数 : ,受制于 。这是一个使用可分离规划来近似凸非线性函数的例子(参见 HP Williams 的《数学规划中的模型构建》[1]——关于非线性模型的章节)。可分离函数是可以分解为单变量函数的函数,而这些函数又可以用分段线性函数来近似。
对于 GLPK 公式,我已经对链接上的正向和负向流量进行了建模,以检查它对两个方向的流量是否都能正常工作。很容易混淆符号并出错,因此检查两种流量是验证该技术的一种好方法。这种技术将应用于所有区域之间的互连器流量,因此需要在以后更复杂的模型中正确使用。
/* Linearised loss model Dr. H J Mackenzie 2010-04-13 Problem description Losses on the link are loss = 0.01 x flow^2 modeled as a 10 step linear approximation [-10, 10] Modeling of loss for loss = k x flow^2 k = 0.01 steps = 10 min flow = -10 max flow = 10 interval flow losses slope midpoint mp losses 10 1 1 -8 0.64 -0.18 -9 0.81 2 -6 0.36 -0.14 -7 0.49 3 -4 0.16 -0.1 -5 0.25 4 -2 0.04 -0.06 -3 0.09 5 0 0 -0.02 -1 0.01 6 2 0.04 0.02 1 0.01 7 4 0.16 0.06 3 0.09 8 6 0.36 0.1 5 0.25 9 8 0.64 0.14 7 0.49 10 10 1 0.18 9 0.81 */ # set of regions set REGIONS; # set of transmission lines set LINES; param line_start {LINES} symbolic in REGIONS; param line_end {LINES} symbolic in REGIONS; param max_flow {LINES} >= 0; param min_flow {LINES} <= 0; param no_loss_segments; set LOSS_SEGMENTS := 1.. no_loss_segments; param loss_segment_length {LINES, LOSS_SEGMENTS} >= 0; param loss_at_minimum_flow {LINES} >= 0; param loss_coefficient {LINES, LOSS_SEGMENTS}; # can be positive or negative # dispatch variables param midline_flow {l in LINES} >= min_flow[l], <= max_flow[l]; var line_loss {LINES}; var loss_segment_dispatch {l in LINES, s in LOSS_SEGMENTS} >= 0 , <= loss_segment_length[l, s]; var line_start_flows {LINES}; var line_end_flows {LINES}; # objective function minimize line_losses: sum {l in LINES} line_loss[l]; # constraints s.t. transmission_line_flow {l in LINES}: sum {s in LOSS_SEGMENTS} loss_segment_dispatch[l, s] + min_flow[l] = midline_flow[l]; s.t. transmission_line_loss {l in LINES}: sum {s in LOSS_SEGMENTS} loss_segment_dispatch[l, s] * loss_coefficient[l, s] + loss_at_minimum_flow[l] = line_loss[l]; s.t. transmission_line_start_flows {l in LINES}: midline_flow[l] + 0.5 * line_loss[l] = line_start_flows[l]; s.t. transmission_line_end_flows {l in LINES}: midline_flow[l] - 0.5 * line_loss[l] = line_end_flows[l]; # solve the problem solve; # output input and determined values printf "\n\nLine losses\n\n%-10s %-10s %10s %10s %10s\n", 'Line', 'Segment', 'Length', 'Coeff', 'Dispatch'; printf {l in LINES, s in LOSS_SEGMENTS} "%-10s %-10d %10d %10.2f %10.2f\n", l, s, loss_segment_length[l, s], loss_coefficient[l, s], loss_segment_dispatch[l, s]; printf "\n\nLine dispatch\n\n%-10s %-10s %-10s %10s %10s %12s %10s %10s %10s %10s\n", 'Line', 'Line start', 'Line end', 'Max flow', 'Min flow', 'Minflow loss', 'Line flow', 'Line start', 'Line end', 'Line loss'; printf {l in LINES} "%-10s %-10s %-10s %10.2f %10.2f %12.2f %10.2f %10.2f %10.2f %10.2f\n", l, line_start[l], line_end[l], max_flow[l], min_flow[l], loss_at_minimum_flow[l], midline_flow[l], line_start_flows[l], line_end_flows[l], line_loss[l]; printf "\n"; data; set REGIONS := 'REGION_A', 'REGION_B'; param : LINES : line_start line_end max_flow min_flow loss_at_minimum_flow midline_flow := 'AtoBneg' 'REGION_A' 'REGION_B' 10 -10 1.0 -6 'AtoBpos' 'REGION_A' 'REGION_B' 10 -10 1.0 6 ; param no_loss_segments := 10; param : loss_segment_length loss_coefficient := 'AtoBneg' 1 2 -0.18 'AtoBneg' 2 2 -0.14 'AtoBneg' 3 2 -0.1 'AtoBneg' 4 2 -0.06 'AtoBneg' 5 2 -0.02 'AtoBneg' 6 2 0.02 'AtoBneg' 7 2 0.06 'AtoBneg' 8 2 0.1 'AtoBneg' 9 2 0.14 'AtoBneg' 10 2 0.18 'AtoBpos' 1 2 -0.18 'AtoBpos' 2 2 -0.14 'AtoBpos' 3 2 -0.1 'AtoBpos' 4 2 -0.06 'AtoBpos' 5 2 -0.02 'AtoBpos' 6 2 0.02 'AtoBpos' 7 2 0.06 'AtoBpos' 8 2 0.1 'AtoBpos' 9 2 0.14 'AtoBpos' 10 2 0.18 ; end;
该解决方案对两个流量方向都一致有效,并在线路中点的名义流量下产生了合理的结果。
* 10: obj = 7.200000000e-001 infeas = 0.000e+000 (0) OPTIMAL SOLUTION FOUND Time used: 0.0 secs Memory used: 0.1 Mb (141776 bytes) Line losses Line Segment Length Coeff Dispatch AtoBneg 1 2 -0.18 2.00 AtoBneg 2 2 -0.14 2.00 AtoBneg 3 2 -0.10 0.00 AtoBneg 4 2 -0.06 0.00 AtoBneg 5 2 -0.02 0.00 AtoBneg 6 2 0.02 0.00 AtoBneg 7 2 0.06 0.00 AtoBneg 8 2 0.10 0.00 AtoBneg 9 2 0.14 0.00 AtoBneg 10 2 0.18 0.00 AtoBpos 1 2 -0.18 2.00 AtoBpos 2 2 -0.14 2.00 AtoBpos 3 2 -0.10 2.00 AtoBpos 4 2 -0.06 2.00 AtoBpos 5 2 -0.02 2.00 AtoBpos 6 2 0.02 2.00 AtoBpos 7 2 0.06 2.00 AtoBpos 8 2 0.10 2.00 AtoBpos 9 2 0.14 0.00 AtoBpos 10 2 0.18 0.00 Line dispatch Line Line start Line end Max flow Min flow Minflow loss Line flow Line start Line end Line loss AtoBneg REGION_A REGION_B 10.00 -10.00 1.00 -6.00 -5.82 -6.18 0.36 AtoBpos REGION_A REGION_B 10.00 -10.00 1.00 6.00 6.18 5.82 0.36
参考文献
[edit | edit source]- ↑ Williams, H. Paul (1999). Model Building in Mathematical Programming. Wiley. ISBN 978-0-471-99788-7.