主要内容

Optimal Dispatch of Power Generators: Problem-Based

This example shows how to schedule two gas-fired electric generators optimally, meaning to get the most revenue minus cost. While the example is not entirely realistic, it does show how to take into account costs that depend on decision timing.

For the solver-based approach to this problem, seeOptimal Dispatch of Power Generators: Solver-Based

问题定义

电力市场在一天中的不同时间有不同的价格。如果您有发电机的发电机,则可以通过安排发电机在价格高时运行来利用此可变价格。假设您控制两个发电机。每个发电机具有三个电源水平(关闭,低和高)。每个发电机在每个电源水平上都有指定的燃油消耗和功率。当发电机关闭时,燃油消耗为0。

You can assign a power level to each generator for each half-hour time interval during a day (24 hours, so 48 intervals). Based on historical records, assume that you know the revenue per megawatt-hour (MWh) that you receive in each time interval. The data for this example is from the Australian Energy Market Operatorhttps://www.nemweb.com.au/REPORTS/CURRENT/in mid-2013, and is used under their termshttps://www.aemo.com.au/隐私和法律通知/版权所有

loaddispatchPrice;% Get poolPrice, which is the revenue per MWhbar(poolPrice,.5) xlim([.5,48.5]) xlabel('Price per MWh at each period')

图包含一个轴对象。轴对象包含类型栏的对象。

There is a cost to start a generator after it has been off. Also, there is a constraint on the maximum fuel usage for the day. This constraint exists because you buy your fuel a day ahead of time, so you can use only what you just bought.

问题符号和参数

You can formulate the scheduling problem as a binary integer programming problem. Define indexesi,j, andk, and a binary scheduling vectory, as follows:

  • nperiods= the number of time periods, 48 in this case.

  • i= a time period, 1 <=i<= 48.

  • j=发电机索引,1 <=j<= 2 for this example.

  • y(i,j,k)= 1什么时候periodi, generatorjis operating at power levelk。让低功率为k = 1, and high power bek = 2。当发电机关闭时sum_k y(i,j,k) = 0

确定发电机何时关闭后开始。为此,定义辅助二进制变量Z(i,j)that indicates whether to charge for turning on generatorj在期间i

  • Z(i,j)= 1什么时候generatorj在期间关闭i, but is on at periodi + 1Z(i,j)= 0otherwise. In other words,Z(i,j)= 1什么时候sum_k y(i,j,k) = 0andsum_k y(i+1,j,k)= 1

You need a way to setzautomatically based on the settings ofy。A linear constraint below handles this setting.

You also need the parameters of the problem for costs, generation levels for each generator, consumption levels of the generators, and fuel available.

  • 游泳池(i)-- Revenue in dollars per MWh in intervali

  • gen(j,k)- 发电机生成的MWj在权力levelk

  • 燃料(J,K)- 发电机使用的燃料j在权力levelk

  • totalFuel-- Fuel available in one day

  • startCost-- Cost in dollars to start a generator after it has been off

  • fuelPrice-- Cost for a unit of fuel

你得到了游泳池什么时候you executedload dispatchPrice;。Set the other parameters as follows.

fuelPrice = 3; totalFuel = 3.95e4; nPeriods = length(poolPrice);% 48 periodsnGens = 2;% Two generatorsgen = [61,152;50,150];%发电机1 LOL = 61 MW,高= 152 MWfuel = [427,806;325,765];% Fuel consumption for generator 2 is low = 325, high = 765startCost = 1e4;% Cost to start a generator after it has been off

Generator Efficiency

检查两个发电机在两个操作点上的效率。

efficiency = gen./fuel;% Calculate electricity per unit fuel userr = efficiency';% for plottingh = bar(rr); h(1).FaceColor ='G';h(2).FaceColor ='c';传奇(h,'Generator 1','Generator 2','Location','NorthEastOutside') ax = gca; ax.XTick = [1,2]; ax.XTickLabel = {'Low','High'};ylim([。1,.2])ylabel('效率')

图包含一个轴对象。轴对象包含2个类型bar的对象。这些对象代表生成器1,生成器2。

Notice that generator 2 is a bit more efficient than generator 1 at its corresponding operating points (low and high), but generator 1 at its high operating point is more efficient than generator 2 at its low operating point.

Variables for Solution

To set up the problem, you need to encode all the problem data and constraints in problem form. The variablesy(i,j,k)表示问题的解决方案和辅助变量Z(i,j)indicate whether to charge for turning on a generator.y是一个nPeriods-by-nGens-by-2array, andz是一个nperiods-by-ngensarray. All variables are binary.

y = optimvar('y',nperiods,ngens,{'Low','High'},,'类型','整数','LowerBound',0,。。。'UpperBound',1); z = optimvar('z',nperiods,ngens,'类型','整数','LowerBound',0,。。。'UpperBound',1);

线性约束

为了确保功率水平不超过一个等于1的组件,请设置线性不等式约束。

powercons = y(:,:,'Low') + y(:,:,'High') <= 1;

The running cost per period is the cost of fuel for that period. For generatorjoperating at levelk, the cost isfuelPrice * fuel(j,k)

Create an expressionfuelUsed那亚柯unts for all the fuel used.

yFuel = zeros(nPeriods,nGens,2); yFuel(:,1,1) = fuel(1,1);% Fuel use of generator 1 in low settingyfuel(:,1,2)=燃料(1,2);% Fuel use of generator 1 in high settingyFuel(:,2,1) = fuel(2,1);% Fuel use of generator 2 in low settingyFuel(:,2,2) = fuel(2,2);%在高环境中使用发电机2的燃料使用fuelUsed = sum(sum(sum(y.*yFuel)));

The constraint is that the fuel used is no more than the fuel available.

FuelCons = Fuelused <= Total Fuluel;

Set the Generator Startup Indicator Variables

How can you get the solver to set thez变量自动匹配活动/关闭周期yvariables? Recall that the condition to satisfy isZ(i,j)= 1exactly whensum_k y(i,j,k) = 0andsum_k y(i+1,j,k)= 1

Notice thatsum_k(-y(i,j,k) + y(i + 1,j,k))> 0exactly when you wantZ(i,j)= 1

Therefore, include these linear inequality constraints in the problem formulation.

sum_k ( - y(i,j,k) + y(i+1,j,k) ) - z(i,j) < = 0

Also, include thez目标函数成本中的变量。与zvariables in the objective function, the solver attempts to lower their values, meaning it tries to set them all equal to 0. But for those intervals when a generator turns on, the linear inequality forcesZ(i,j)to equal 1.

创建一个辅助变量w代表y(i+1,j,k) - y(i,j,k)。Represent the generator startup inequality in terms ofw

w = optimexpr(nPeriods,nGens);% Allocate widx = 1:(nPeriods-1); w(idx,:) = y(idx+1,:,'Low') - y(idx,:,'Low') + y(idx+1,:,'High') - y(idx,:,'High');w(nperiods,:) = y(1,:,,,,'Low') - y(nPeriods,:,'Low') + y(1,:,'High') - y(nPeriods,:,'High');switchcons = w - z <= 0;

Define Objective

The objective function includes fuel costs for running the generators, revenue from running the generators, and costs for starting the generators.

generatorlevel = zeros(size(yFuel)); generatorlevel(:,1,1) = gen(1,1);% Fill in the levelsgeneratorlevel(:,1,2) = gen(1,2); generatorlevel(:,2,1) = gen(2,1); generatorlevel(:,2,2) = gen(2,2);

收入= y。*GeneratorLevel。*PoolPrice

revenue = optimexpr(size(y));forii = 1:nPeriods revenue(ii,:,:) = poolPrice(ii)*y(ii,:,:).*generatorlevel(ii,:,:);end

总燃料成本=fuelUsed*fuelPrice

fuelCost = fuelUsed*fuelPrice;

The generator startup cost =z*startCost

startingCost = z*startCost;

The profit = incoming revenue-total fuel cost-启动成本。

profit = sum(sum(sum(revenue))) - fuelCost - sum(sum(startingCost));

Solve the Problem

创建优化问题,并包括目标和约束。

dispatch = optimproblem('ObjectiveSense','最大化');dispatch.Objective =利润;dispatch.constraints.switchcons = switchCons;dispatch.constraints.fuelcons = fuelcons;dispatch.constraints.powercons = powerCons;

要节省空间,请抑制迭代显示。

选项= optimoptions('intlinprog','展示','最后');

解决这个问题。

[dispatchsol,fval,exitflag,output] = solve(dispatch,'选项',选项);
Solving problem using intlinprog. Optimal solution found. Intlinprog stopped because the objective value is within a gap tolerance of the optimal value, options.RelativeGapTolerance = 0.0001 (the default value). The intcon variables are integer within tolerance, options.IntegerTolerance = 1e-05 (the default value).

Examine the Solution

Plot the solution as a function of time.

子图(3,1,1)bar(dispatchsol.y(:,1,1)*gen(1,1)+dispatchsol.y(:,1,2)*gen(1,2)'G') xlim([.5,48.5]) ylabel('MWh') 标题('Generator 1 Optimal Schedule','FontWeight','bold') subplot(3,1,2) bar(dispatchsol.y(:,2,1)*gen(2,1)+dispatchsol.y(:,2,2)*gen(2,2),.5,'c') 标题('Generator 2 Optimal Schedule','FontWeight','bold') xlim([.5,48.5]) ylabel('MWh') subplot(3,1,3) bar(poolPrice,.5) xlim([.5,48.5]) title(“能源价格”,'FontWeight','bold') xlabel('Period') ylabel('$ / MWh')

Figure contains 3 axes objects. Axes object 1 with title Generator 1 Optimal Schedule contains an object of type bar. Axes object 2 with title Generator 2 Optimal Schedule contains an object of type bar. Axes object 3 with title Energy Price contains an object of type bar.

Generator 2 runs longer than generator 1, which you would expect because it is more efficient. Generator 2 runs at its high power level whenever it is on. Generator 1 runs mainly at its high power level, but dips down to low power for one time unit. Each generator runs for one contiguous set of periods daily, and, therefore, incurs only one startup cost each day.

Check that thezvariable is 1 for the periods when the generators start.

starttimes = find(round(dispatchsol.z) == 1);% Use round for noninteger results[theperiod,thegenerator] = ind2sub(size(dispatchsol.z),starttimes)
period =2×123 16
thegenerator =2×11 2

The periods when the generators start match the plots.

Compare to Lower Penalty for Startup

If you specify a lower value forstartCost, the solution involves multiple generation periods.

startCost = 500;% Choose a lower penalty for starting the generatorsstartingCost = z*startCost; profit = sum(sum(sum(revenue))) - fuelCost - sum(sum(startingCost)); dispatch.Objective = profit; [dispatchsolnew,fvalnew,exitflagnew,outputnew] = solve(dispatch,'选项',选项);
Solving problem using intlinprog. Optimal solution found. Intlinprog stopped because the objective value is within a gap tolerance of the optimal value, options.RelativeGapTolerance = 0.0001 (the default value). The intcon variables are integer within tolerance, options.IntegerTolerance = 1e-05 (the default value).
subplot(3,1,1) bar(dispatchsolnew.y(:,1,1)*gen(1,1)+dispatchsolnew.y(:,1,2)*gen(1,2),.5,'G') xlim([.5,48.5]) ylabel('MWh') 标题('Generator 1 Optimal Schedule','FontWeight','bold')子图(3,1,2)bar(dispatchsolnew.y(:,2,1)*gen(2,1)+dispatchsolnew.y(:,2,2)*gen(2,2)'c') 标题('Generator 2 Optimal Schedule','FontWeight','bold') xlim([.5,48.5]) ylabel('MWh') subplot(3,1,3) bar(poolPrice,.5) xlim([.5,48.5]) title(“能源价格”,'FontWeight','bold') xlabel('Period') ylabel('$ / MWh')

Figure contains 3 axes objects. Axes object 1 with title Generator 1 Optimal Schedule contains an object of type bar. Axes object 2 with title Generator 2 Optimal Schedule contains an object of type bar. Axes object 3 with title Energy Price contains an object of type bar.

starttimes = find(round(dispatchsolnew.z) == 1);% Use round for noninteger results[theperiod,thegenerator] = ind2sub(size(dispatchsolnew.z),starttimes)
period =3×122 16 45
thegenerator =3×11 22

Related Topics