Esta página aún no se ha traducido para esta versión. Puede ver la versión más reciente de esta página en inglés.

Envío óptimo de generadores de energía: basado en Solver

Este ejemplo muestra cómo programar dos generadores eléctricos de gas de forma óptima, lo que significa obtener la mayor cantidad de ingresos menos el costo. Si bien el ejemplo no es totalmente realista, muestra cómo tener en cuenta los costos que dependen del tiempo de decisión.

Para conocer el enfoque basado en problemas de este problema, consulte.Envío óptimo de generadores de energía: basado en problemas

Definición de problema

El mercado de la electricidad tiene diferentes precios en diferentes momentos del día. Si tiene generadores, puede aprovechar este precio variable programando sus generadores para que funcionen cuando los precios son altos. Supongamos que hay dos generadores que usted controla. Cada generador tiene tres niveles de potencia (desactivado, bajo y alto). Cada generador tiene una tasa especificada de consumo de combustible y producción de energía en cada nivel de potencia. Por supuesto, el consumo de combustible es 0 cuando el generador está apagado.

Puede asignar un nivel de potencia a cada generador durante cada intervalo de tiempo de media hora durante un día (24 horas, por lo que 48 intervalos). En función de los registros históricos, puede suponer que conoce los ingresos por megavatio-hora (MWh) que obtiene en cada intervalo de tiempo. Los datos de este ejemplo provienen del operador australiano del mercado de la energía a mediados de 2013, y se utilizan bajo sus términos.https://www.nemweb.com.au/REPORTS/CURRENT/https://www.aemo.com.au/About-AEMO/Legal-Notices/Copyright-Permissions

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

Hay un costo para iniciar un generador después de que se ha apagado. La otra restricción es un consumo máximo de combustible para el día. La restricción de combustible máxima es porque usted compra su combustible un día antes de tiempo, por lo que puede utilizar sólo lo que acaba de comprar.

Notación de problemas y parámetros

Puede formular el problema de programación como un problema de programación de enteros binarios como se indica a continuación. Defina índices y un vector de programación binario como:ijky

  • = el número de periodos de tiempo, 48 en este caso.nPeriods

  • = un período de tiempo, 1 < = < = 48.ii

  • = un índice del generador, 1 < = < = 2 para este ejemplo.jj

  • Cuando el período, el generador está operando en el nivel de potencia.y(i,j,k) = 1ijk Deja que la energía sea baja y alta potencia.k = 1k = 2 El generador está apagado cuando.sum_k y(i,j,k) = 0

Usted necesita determinar cuando un generador comienza después de estar apagado. Dejar

  • Cuando el generador está apagado en el período, pero está en el período. Lo contrario.z(i,j) = 1jii + 1z(i,j) = 0 En otras palabras, cuando y.z(i,j) = 1sum_k y(i,j,k) = 0sum_k y(i+1,j,k) = 1

Obviamente, necesita una forma de establecer automáticamente en función de la configuración de.zy Una restricción lineal a continuación controla esta configuración.

También necesita los parámetros del problema para los costos, los niveles de generación para cada generador, los niveles de consumo de los generadores y el combustible disponible.

  • --Ingresos en dólares por MWh en intervalo.poolPrice(i)i

  • --MW generados por el generador a nivel de potencia.gen(j,k)jk

  • --Combustible utilizado por el generador a nivel de potencia.fuel(j,k)jk

  • --Combustible disponible en un día.totalfuel

  • --Costo en dólares para iniciar un generador después de que se ha apagado.startCost

  • --Costo para una unidad de combustible.fuelPrice

Tienes cuando ejecutaron.poolPriceload dispatchPrice; Establezca los otros parámetros de la siguiente manera.

fuelPrice = 3; totalfuel = 3.95e4; nPeriods = length(poolPrice); % 48 periods nGens = 2; % Two generators gen = [61,152;50,150]; % Generator 1 low = 61 MW, high = 152 MW fuel = [427,806;325,765]; % Fuel consumption for generator 2 is low = 325, high = 765 startCost = 1e4; % Cost to start a generator after it has been off

Eficiencia del generador

Examine la eficiencia de los dos generadores en sus dos puntos de operación.

efficiency = gen./fuel; % Calculate electricity per unit fuel use rr = efficiency'; % for plotting h = bar(rr); h(1).FaceColor = 'g'; h(2).FaceColor = 'c'; legend(h,'Generator 1','Generator 2','Location','NorthEastOutside') ax = gca; ax.XTick = [1,2]; ax.XTickLabel = {'Low','High'}; ylim([.1,.2]) ylabel('Efficiency')

Observe que el generador 2 es un poco más eficiente que el generador 1 en sus puntos de operación correspondientes (bajo o alto), pero el generador 1 en su punto de funcionamiento alto es más eficiente que el generador 2 en su punto de funcionamiento bajo.

Variables para la solución

Para configurar el problema, debe codificar todos los datos del problema y las restricciones en el formulario que requiere el solucionador.intlinprog Tiene variables que representan la solución del problema, y variables auxiliares para la carga para encender un generador. es una matriz y es una matriz.y(i,j,k)z(i,j)ynPeriods-by-nGens-by-2znPeriods-by-nGens

Para poner estas variables en un vector largo, defina la variable de desconocidos:x

x = [y(:);z(:)];

Para los límites y las restricciones lineales, es más fácil utilizar la formulación de matriz natural de y, a continuación, convertir las restricciones a la variable de decisión total, el vector.yzx

Límites

El vector de solución consta de variables binarias.x Configure los límites y.lbub

lby = zeros(nPeriods,nGens,2); % 0 for the y variables lbz = zeros(nPeriods,nGens); % 0 for the z variables lb = [lby(:);lbz(:)]; % Column vector lower bound ub = ones(size(lb)); % Binary variables have lower bound 0, upper bound 1

Restricciones lineales

Para las restricciones lineales, el número de columnas de la matriz debe ser el mismo que la longitud de, que es el mismo que la longitud de.A*x <= bAxlb Para crear filas del tamaño adecuado, cree matrices de cero de los tamaños de las matrices y.Ayz

cleary = zeros(nPeriods,nGens,2); clearz = zeros(nPeriods,nGens);

Para asegurarse de que el nivel de potencia no tenga más de un componente igual a 1, establezca una restricción de desigualdad lineal:

x(i,j,1) + x(i,j,2) <= 1

A = spalloc(nPeriods*nGens,length(lb),2*nPeriods*nGens); % nPeriods*nGens inequalities counter = 1; for ii = 1:nPeriods     for jj = 1:nGens         temp = cleary;         temp(ii,jj,:) = 1;         addrow = [temp(:);clearz(:)]';         A(counter,:) = sparse(addrow);         counter = counter + 1;     end end b = ones(nPeriods*nGens,1); % A*x <= b means no more than one of x(i,j,1) and x(i,j,2) are equal to 1

El costo de funcionamiento por período es el costo del combustible para ese período. Para el generador que opera a nivel, el costo es.jkfuelPrice * fuel(j,k)

Para garantizar que los generadores no utilicen demasiado combustible, cree una restricción de desigualdad en la suma del consumo de combustible.

yFuel = lby; % Initialize fuel usage array yFuel(:,1,1) = fuel(1,1); % Fuel use of generator 1 in low setting yFuel(:,1,2) = fuel(1,2); % Fuel use of generator 1 in high setting yFuel(:,2,1) = fuel(2,1); % Fuel use of generator 2 in low setting yFuel(:,2,2) = fuel(2,2); % Fuel use of generator 2 in high setting  addrow = [yFuel(:);clearz(:)]'; A = [A;sparse(addrow)]; b = [b;totalfuel]; % A*x <= b means the total fuel usage is <= totalfuel

Establezca las variables del indicador de inicio del generador

¿Cómo puede obtener el solucionador para establecer las variables automáticamente para que coincidan con los periodos de actividad/apagado que representan las variables?zy Recuerde que la condición para satisfacer es exactamente cuandoz(i,j) = 1

Y.sum_k y(i,j,k) = 0sum_k y(i+1,j,k) = 1

Observe que

exactamente cuando quieras.sum_k ( - y(i,j,k) + y(i+1,j,k) ) > 0z(i,j) = 1

Por lo tanto, incluya las restricciones de desigualdad lineales

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

en la formulación del problema e incluyen las variables en el coste de la función objetiva.z Al incluir las variables en la función objetivo, el solucionador intenta reducir los valores de las variables, lo que significa que intenta establecerlas todas iguales a 0.zz Pero para esos intervalos cuando un generador se enciende, la desigualdad lineal obliga a igualar 1.z(i,j)

Agregue filas adicionales a la matriz de restricciones de desigualdad lineal para representar estas nuevas desigualdades.A Envolver alrededor del tiempo para que el intervalo 1 siga lógicamente el intervalo 48.

tempA = spalloc(nPeriods*nGens,length(lb),2*nPeriods*nGens); counter = 1; for ii = 1:nPeriods     for jj = 1:nGens         temp = cleary;         tempy = clearz;         temp(ii,jj,1) = -1;         temp(ii,jj,2) = -1;         if ii < nPeriods % Intervals 1 to 47             temp(ii+1,jj,1) = 1;             temp(ii+1,jj,2) = 1;         else % Interval 1 follows interval 48             temp(1,jj,1) = 1;             temp(1,jj,2) = 1;         end         tempy(ii,jj) = -1;         temp = [temp(:);tempy(:)]'; % Row vector for inclusion in tempA matrix         tempA(counter,:) = sparse(temp);         counter = counter + 1;     end end A = [A;tempA]; b = [b;zeros(nPeriods*nGens,1)]; % A*x <= b sets z(i,j) = 1 at generator startup

Sparsity of Constraints

Si tiene un problema grande, el uso de matrices de restricciones dispersas ahorra memoria y también puede ahorrar tiempo de cálculo. La matriz de restricciones es bastante escasa:A

filledfraction = nnz(A)/numel(A)
filledfraction = 0.0155 

acepta matrices de restricciones lineales dispersas y, pero requiere sus limitaciones vectoriales correspondientes y estar lleno.intlinprogAAeqbbeq

Definir objetivo

La función objetiva incluye los costos de combustible para la ejecución de los generadores, los ingresos de la ejecución de los generadores y los costos de arranque de los generadores.

generatorlevel = lby; % Generation in MW, start with 0s generatorlevel(:,1,1) = gen(1,1); % Fill in the levels generatorlevel(:,1,2) = gen(1,2); generatorlevel(:,2,1) = gen(2,1); generatorlevel(:,2,2) = gen(2,2);

Ingresos entrantes =x.*generatorlevel.*poolPrice

revenue = generatorlevel; % Allocate revenue array for ii = 1:nPeriods     revenue(ii,:,:) = poolPrice(ii)*generatorlevel(ii,:,:); end

Coste total del combustible =y.*yFuel*fuelPrice

fuelCost = yFuel*fuelPrice;

Costo inicial =z.*ones(size(z))*startCost

starts = (clearz + 1)*startCost; starts = starts(:); % Generator startup cost vector

El vector.x = [y(:);z(:)] Anote el beneficio total en términos de:x

beneficio = ingresos entrantes-costo total del combustible-costo de inicio

f = [revenue(:) - fuelCost(:);-starts]; % f is the objective function vector

Resolver el problema

Para ahorrar espacio, suprima la visualización iterativa.

options = optimoptions('intlinprog','Display','final'); [x,fval,eflag,output] = intlinprog(-f,1:length(f),A,b,[],[],lb,ub,options);
Optimal solution found.  Intlinprog stopped because the objective value is within a gap tolerance of the optimal value, options.AbsoluteGapTolerance = 0 (the default value). The intcon variables are integer within tolerance, options.IntegerTolerance = 1e-05 (the default value). 

Examine la solución

La forma más fácil de examinar la solución es dividir el vector de solución en sus dos componentes y.xyz

ysolution = x(1:nPeriods*nGens*2); zsolution = x(nPeriods*nGens*2+1:end); ysolution = reshape(ysolution,[nPeriods,nGens,2]); zsolution = reshape(zsolution,[nPeriods,nGens]);

Trace la solución en función del tiempo.

subplot(3,1,1) bar(ysolution(:,1,1)*gen(1,1)+ysolution(:,1,2)*gen(1,2),.5,'g') xlim([.5,48.5]) ylabel('MWh') title('Generator 1 optimal schedule','FontWeight','bold') subplot(3,1,2) bar(ysolution(:,2,1)*gen(1,1)+ysolution(:,2,2)*gen(1,2),.5,'c') title('Generator 2 optimal schedule','FontWeight','bold') xlim([.5,48.5]) ylabel('MWh') subplot(3,1,3) bar(poolPrice,.5) xlim([.5,48.5]) title('Energy price','FontWeight','bold') xlabel('Period') ylabel('$ / MWh')

El generador 2 corre más tiempo que el generador 1, que cabría esperar porque es más eficiente. El generador 2 se ejecuta en su nivel de alta potencia cada vez que está encendido. El generador 1 se ejecuta principalmente en su nivel de potencia alta, pero se extiende a baja potencia para una unidad de tiempo. Cada generador se ejecuta para un conjunto contiguo de períodos diarios, por lo que solo se incurde en un costo de inicio.

Compruebe que la variable es 1 para los períodos en que se inician los generadores.z

starttimes = find(round(zsolution) == 1); % Use round for noninteger results [theperiod,thegenerator] = ind2sub(size(zsolution),starttimes)
theperiod = 2×1

    23
    16

thegenerator = 2×1

     1
     2

Los períodos en que los generadores empiezan a coincidir con las parcelas.

Comparar con baja penalización para startup

Si elige un valor pequeño, la solución implica varios periodos de generación.startCost

startCost = 500; % Choose a lower penalty for starting the generators starts = (clearz + 1)*startCost; starts = starts(:); % Start cost vector fnew = [revenue(:) - fuelCost(:);-starts]; % New objective function [xnew,fvalnew,eflagnew,outputnew] = ...     intlinprog(-fnew,1:length(fnew),A,b,[],[],lb,ub,options);
Optimal solution found.  Intlinprog stopped because the objective value is within a gap tolerance of the optimal value, options.AbsoluteGapTolerance = 0 (the default value). The intcon variables are integer within tolerance, options.IntegerTolerance = 1e-05 (the default value). 
 ysolutionnew = xnew(1:nPeriods*nGens*2); zsolutionnew = xnew(nPeriods*nGens*2+1:end); ysolutionnew = reshape(ysolutionnew,[nPeriods,nGens,2]); zsolutionnew = reshape(zsolutionnew,[nPeriods,nGens]);  subplot(3,1,1) bar(ysolutionnew(:,1,1)*gen(1,1)+ysolutionnew(:,1,2)*gen(1,2),.5,'g') xlim([.5,48.5]) ylabel('MWh') title('Generator 1 optimal schedule','FontWeight','bold') subplot(3,1,2) bar(ysolutionnew(:,2,1)*gen(1,1)+ysolutionnew(:,2,2)*gen(1,2),.5,'c') title('Generator 2 optimal schedule','FontWeight','bold') xlim([.5,48.5]) ylabel('MWh') subplot(3,1,3) bar(poolPrice,.5) xlim([.5,48.5]) title('Energy price','FontWeight','bold') xlabel('Period') ylabel('$ / MWh')

 starttimes = find(round(zsolutionnew) == 1); % Use round for noninteger results [theperiod,thegenerator] = ind2sub(size(zsolution),starttimes)
theperiod = 3×1

    22
    16
    45

thegenerator = 3×1

     1
     2
     2

Temas relacionados