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 problemas

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 el solucionador de este problema, consulte.Envío óptimo de generadores de energía: basado en Solver

Definición de problema

El mercado de la electricidad tiene diferentes precios en diferentes momentos del día. Si tiene generadores que suministran electricidad, puede aprovechar este precio variable programando sus generadores para que funcionen cuando los precios son altos. Supongamos que controlas dos generadores. 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. El consumo de combustible es 0 cuando el generador está apagado.

Puede asignar un nivel de potencia a cada generador para 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, suponga que conoce los ingresos por megavatio-hora (MWh) que recibe en cada intervalo de tiempo. Los datos de este ejemplo provienen del operador australiano del mercado de la energía https://www.nemweb.com.au/REPORTS/CURRENT/ a mediados de 2013, y se utiliza bajo sus términos https://www.aemo.com.au/Privacy_and_Legal_Notices/Copyright_Permissions_Notice.

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. Además, hay una restricción en el consumo máximo de combustible para el día. Esta restricción existe 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. Defina índices, y, y un vector de programación binario, como se indica a continuación: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

Determine Cuándo se inicia un generador después de apagarse. Para ello, defina la variable binaria auxiliar que indica si se cobra por encender el generador en el período.z(i,j)ji

  • 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

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 intervalopoolPrice(i)i

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

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

  • --Combustible disponible en un díatotalFuel

  • --Costo en dólares para iniciar un generador después de haber estado apagadostartCost

  • --Costo de una unidad de combustiblefuelPrice

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 y 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 forma de problema. Las variables representan la solución del problema, y las variables auxiliares indican si se 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 Todas las variables son binarias.

y = optimvar('y',nPeriods,nGens,{'Low','High'},'Type','integer','LowerBound',0,...     'UpperBound',1); z = optimvar('z',nPeriods,nGens,'Type','integer','LowerBound',0,...     'UpperBound',1);

Restricciones lineales

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

powercons = y(:,:,'Low') + y(:,:,'High') <= 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)

Cree una expresión que representa todo el combustible utilizado.fuelUsed

yFuel = zeros(nPeriods,nGens,2); 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  fuelUsed = sum(sum(sum(y.*yFuel)));

La restricción es que el combustible utilizado no es más que el combustible disponible.

fuelcons = fuelUsed <= 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 de las variables?zy Recuerde que la condición para satisfacer es exactamente cuándo y.z(i,j) = 1sum_k y(i,j,k) = 0sum_k y(i+1,j,k) = 1

Observe que exactamente cuando usted quiere.sum_k ( - y(i,j,k) + y(i+1,j,k) ) > 0z(i,j) = 1

Por lo tanto, incluya estas restricciones de desigualdad lineales en la formulación del problema.

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

Además, incluya las variables en el coste de la función objetiva.z Con las variables en la función objetivo, el solucionador intenta reducir sus valores, lo que significa que intenta establecerlos todos igual a 0.z Pero para esos intervalos cuando un generador se enciende, la desigualdad lineal obliga a igualar 1.z(i,j)

Cree una variable auxiliar que represente.wy(i+1,j,k) - y(i,j,k) Representar la desigualdad de arranque del generador en términos de.w

w = optimexpr(nPeriods,nGens); % Allocate w idx = 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;

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  = zeros(size(yFuel)); 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 = y..*generatorlevel.*poolPrice

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

El costo total del combustible =.fuelUsed*fuelPrice

fuelCost = fuelUsed*fuelPrice;

El costo inicial del generador =.z*startCost

startingCost = z*startCost;

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

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

Resolver el problema

Cree un problema de optimización e incluya el objetivo y las restricciones.

dispatch = optimproblem('ObjectiveSense','maximize'); dispatch.Objective = profit; dispatch.Constraints.switchcons = switchcons; dispatch.Constraints.fuelcons = fuelcons; dispatch.Constraints.powercons = powercons;

Para ahorrar espacio, suprima la visualización iterativa.

options = optimoptions('intlinprog','Display','final');

Resuelve el problema.

[dispatchsol,fval,exitflag,output] = solve(dispatch,'options',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

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

subplot(3,1,1) bar(dispatchsol.y(:,1,1)*gen(1,1)+dispatchsol.y(:,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(dispatchsol.y(:,2,1)*gen(1,1)+dispatchsol.y(:,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 diariamente y, por lo tanto, solo incure en un costo de inicio cada día.

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

starttimes = find(round(dispatchsol.z) == 1); % Use round for noninteger results [theperiod,thegenerator] = ind2sub(size(dispatchsol.z),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 especifica un valor inferior para, la solución implica varios periodos de generación.startCost

startCost = 500; % Choose a lower penalty for starting the generators startingCost = z*startCost; profit = sum(sum(sum(revenue))) - fuelCost - sum(sum(startingCost)); dispatch.Objective = profit; [dispatchsolnew,fvalnew,exitflagnew,outputnew] = solve(dispatch,'options',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). 
 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') title('Generator 1 Optimal Schedule','FontWeight','bold') subplot(3,1,2) bar(dispatchsolnew.y(:,2,1)*gen(1,1)+dispatchsolnew.y(:,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(dispatchsolnew.z) == 1); % Use round for noninteger results [theperiod,thegenerator] = ind2sub(size(dispatchsolnew.z),starttimes)
theperiod = 3×1

    22
    16
    45

thegenerator = 3×1

     1
     2
     2