//====Modificado metodos: CreateDataColumnList y CreateDataConversionList===
//====Micho@29/5==mvarela@adme.com.uy=======================================
unit ugter_onoffporpaso_conrestricciones;

interface

uses
  SysUtils, Classes, xmatdefs, uGTer, unodos,
  uEstados,
  uglobs,
  umipsimplex,
  usimplex,
  ufichasLPD,
  ufechas,
  ucosa, uCosaConNombre,
  uconstantesSimSEE, uFuentesAleatorias;

resourcestring
  rsGeneradorTermicoEYATiempoRestringido =
    'Generador térmico con encendido y apagado por paso de tiempo restringido';

type

  { TFichaGTer_OnOffPorPaso_ConRestricciones }

  TFichaGTer_OnOffPorPaso_ConRestricciones = class(TFichaLPD)
  public
    (**************************************************************************)
    (*               A T R I B U T O S   P E R S I S T E N T E S              *)
    (**************************************************************************)

    PMin, PMax: NReal; // [MW] Potencias Mínima y Máxima Por maquina
    cv_min, cv: NReal; // Costo:= cv_min* Pmin+ cv* (P-Pmin)
    indicePreciosPorCombustible: TFuenteAleatoria;
    bornePreciosPorCombustible: string;
    disp: NReal; // disponibilidad (fortuita)
    tRepHoras: NReal;
    HayRestriccionEmaxPasoDeTiempo: boolean; // indica si se aplica la restricción
    EmaxPasoDeTiempo: NReal; // Energía maxima generable en un paso de tiempo
    minimoNPasosOn, minimoNPasosOff: integer;
    decisionOnOff_PorCiclo, decisionOffOn_PorCiclo: boolean;
    costoArranque, costoParada, costoPorPasoOn, costoPorPasoOff: NReal;

    (**************************************************************************)

    nroBornePreciosPorCombustible: integer;

    constructor Create(capa: integer; fecha: TFecha; periodicidad: TPeriodicidad;
      PMin, PMax: NReal; cv_min, cv: NReal;
      indicePreciosPorCombustible: TFuenteAleatoria; bornePreciosPorCombustible: string;
      disp: NReal; tRepHoras: NReal; HayRestriccionEmaxPasoDeTiempo: boolean;
      EmaxPasoDeTiempo: NReal; minimoNPasosOn, minimoNPasosOff: integer;
      decisionPasosOnPorCiclo, decisionPasosOffPorCiclo: boolean;
      costoArranque, costoParada, costoPorPasoOn, costoPorPasoOff: NReal);

     
    function Rec: TCosa_RecLnk; override;
    procedure BeforeRead(version, id_hilo: integer); override;
    procedure AfterRead(f:TArchiTexto); override;

    procedure generarLineaResumen(var archi: TextFile); override;
    function infoAd_: string; override;
    procedure Free; override;
  end;

  //TGTer_OnOffPorPaso es una potencia minima y maxima constante en todos los postes
  //y un costo variable cv [USD/MWh] tambien igual en todos los postes
  //y un costo variable cv_min "hasta el minimo" igual al costo de produccion en USD/MWh cuando
  //la central esta generado en el minimo.

  { TGTer_OnOffPorPaso_ConRestricciones }

  TGTer_OnOffPorPaso_ConRestricciones = class(TGTer)

  public

    (**************************************************************************)
    (*               A T R I B U T O S   P E R S I S T E N T E S              *)
    (**************************************************************************)

    xOnOff_AlInicio: integer;

    (**************************************************************************)

    pa: TFichaGTer_OnOffPorPaso_ConRestricciones;
    CostoFijoDelPaso, CostoVariableDelPaso: NReal;
    // Solo permitimos 0 o 1.
    NMaquinasDespachadas_estePaso: integer;
    // cantidad de máquinas despachadas ( variable de Acople )
    NMaquinasDespachadas_pasoAnterior: integer;
    NMaquinasArrancadas: integer; // se calcula en opt_leerSolucion y indica la cantidad
    // de máquinas que se arrancaron (si es >0 ) o que se pararon (si es < 0) en el paso.

    // Variable de estado
    // Es el valor de pasos de simulación que lleva ON o OFF la máquina.
    X_OnOff, Xs_OnOff: integer;

    // libre indica que estamos libres de cambiar de estado la máquina.
    // esta variable se calcula en prepararpaso_ps
    libre: boolean;

(* xOnOff es la variable de estado.
si x <= 0, -x+1 es la cantidad de pasos de tiempo que lleva apagada la máquina.
si x > 0 , x es la cantidad de pasos de tiempo que lleva prendida la máquina.

como el manejador de estado está pensado para manejar variables discretas
de 0..N hay que traducir la variable con xTou y uTox
La variable u va de 0..minimoNCiclosOff+minimoNCiclosOn-1
*)

    c0: NReal;//Costo en USD/h por máquina por estar operando en el mínimo técnico
    cv: NReal; //USD/MWh usado para la optimización del paso.
    PMin: NReal; //MW, mínimo
    PxMax: NReal; // MW, cota sup de P-Pmin

    // resultado de los sorteos de disponibilidad y del mantenimiento programado
    NMaquinasDisponibles: integer;
    PMaxDisponible: NReal;

    //Cuanta potencia despacho en cada hora el generador en promedio
    potMedia_despachada: NReal;

    //Variación de costo futuro por transición de estado en USD
    dCF: NReal;



    constructor Create(capa: integer; nombre: string; nacimiento, muerte: TFecha;
      lpdUnidades, lpd: TFichasLPD; nodo: TNodo;
  flg_CalcularGradienteDeInversion: boolean; estadoInicialEncendido: boolean;
  nroPasosEnEstadoInicial: integer; TonCO2xMWh: NReal; LowCostMustRun,
  CleanDevelopmentMechanism: boolean; xFuenteIdxP: TFuenteAleatoria;
  xBorneIdxP: string); reintroduce;


    function Rec: TCosa_RecLnk; override;
    procedure BeforeRead(version, id_hilo: integer); override;
    procedure AfterRead(f:TArchiTexto); override;

    procedure PrepararMemoria( Catalogo: TCatalogoReferencias; globs: TGlobs); override;
    procedure RegistrarParametrosDinamicos( CatalogoReferencias: TCatalogoReferencias ); override;

    function PotenciaFirme: NReal; override;
    function InfoAd_: string; override;
    class function DescClase: string; override;

    procedure PosicionarseEnEstrellita; override;
    procedure ActualizarEstadoGlobal(flg_Xs: boolean); override;

    procedure Sim_Cronica_Inicio; override;

    function get_pa_FD(kTipoUnidad: integer): NReal; override;
    function get_pa_TMR(kTipoUnidad: integer): NReal; override;

    procedure SorteosDelPaso(sortear: boolean); override;
    procedure PrepararPaso_ps; override;
    procedure Sim_Paso_Fin; override;

    procedure opt_nvers(var ivar, ivae, ires: integer); override;
    procedure optx_nvxs(var ixr, ixd, iauxNReal, iauxInt: integer); override;
    procedure optx_RegistrarVariablesDeEstado(adminEstados: TAdminEstados);
      override;

    procedure opt_cargue(s: TSimplex); override;

    procedure EvolucionarEstado; override;

    procedure opt_fijarRestriccionesDeCaja(s: TSimplex); override;
    procedure opt_leerSolucion(s: TSimplex); override;

    function getNombreVar(ivar: integer; var nombre: string): boolean; override;
    function getNombreRes(ires: integer; var nombre: string): boolean; override;

    procedure Free; override;

    procedure PubliVars; override;
    procedure dump_Variables(var f: TextFile; charIndentacion: char); override;

    class function TipoFichaLPD: TClaseDeFichaLPD; override;


    function xTou(x: integer): integer;
    function uTox(u: integer): integer;

    {$IFDEF BOSTA}
    procedure AfterInstantiation; override;
  {$ENDIF}
  end;

procedure AlInicio;
procedure AlFinal;

implementation

//----------------------------------------------------
// Métodos de TFichaGTer_OnOffPorPaso_ConRestricciones
//====================================================
constructor TFichaGTer_OnOffPorPaso_ConRestricciones.Create(capa: integer;
  fecha: TFecha; periodicidad: TPeriodicidad; PMin, PMax: NReal;
  cv_min, cv: NReal; indicePreciosPorCombustible: TFuenteAleatoria;
  bornePreciosPorCombustible: string; disp: NReal; tRepHoras: NReal;
  HayRestriccionEmaxPasoDeTiempo: boolean; EmaxPasoDeTiempo: NReal;
  minimoNPasosOn, minimoNPasosOff: integer;
  decisionPasosOnPorCiclo, decisionPasosOffPorCiclo: boolean;
  costoArranque, costoParada, costoPorPasoOn, costoPorPasoOff: NReal);
begin
  inherited Create(capa, fecha, periodicidad);
  self.PMin := PMin;
  self.PMax := PMax;
  self.cv_min := cv_min;
  self.cv := cv;
  self.indicePreciosPorCombustible := indicePreciosPorCombustible;
  self.bornePreciosPorCombustible := bornePreciosPorCombustible;
  self.disp := disp;
  self.tRepHoras := tRepHoras;
  self.HayRestriccionEmaxPasoDeTiempo := HayRestriccionEmaxPasoDeTiempo;
  self.EmaxPasoDeTiempo := EmaxPasoDeTiempo;

  self.minimoNPasosOn := minimoNPasosOn;
  self.minimoNPasosOff := minimoNPasosOff;
  Self.decisionOnOff_PorCiclo := decisionPasosOnPorCiclo;
  Self.decisionOffOn_PorCiclo := decisionPasosOffPorCiclo;
  self.costoArranque := costoArranque;
  Self.costoParada := costoParada;
  self.costoPorPasoOn := costoPorPasoOn;
  self.costoPorPasoOff := costoPorPasoOff;

end;

function TFichaGTer_OnOffPorPaso_ConRestricciones.Rec: TCosa_RecLnk;
begin
  Result:=inherited Rec;
  Result.addCampoDef('PMin', PMin);
  Result.addCampoDef('PMax', PMax);
  Result.addCampoDef('cv_min', cv_min);
  Result.addCampoDef('cv', cv);
  Result.addCampoDef_ref('indicePreciosPorCombustible',  TCosa(indicePreciosPorCombustible), Self);
  Result.addCampoDef('bornePreciosPorCombustible', bornePreciosPorCombustible);
  Result.addCampoDef('disp', disp);
  Result.addCampoDef('tRepHoras', tRepHoras);
  Result.addCampoDef('HayRestriccionEmaxPasoDeTiempo', HayRestriccionEmaxPasoDeTiempo);
  Result.addCampoDef('EmaxPasoDeTiempo', EmaxPasoDeTiempo);
  Result.addCampoDef('minimoNPasosOn', minimoNPasosOn);
  Result.addCampoDef('minimoNPasosOff', minimoNPasosOff);
  Result.addCampoDef('decisionPasosOnPorCiclo', decisionOnOff_PorCiclo, 0, 43 );
  Result.addCampoDef('decisionPasosOffPorCiclo', decisionOffOn_PorCiclo, 0, 43 );
  Result.addCampoDef('decisionOnOff_PorCiclo', decisionOnOff_PorCiclo, 43 );
  Result.addCampoDef('decisionOffOn_PorCiclo', decisionOffOn_PorCiclo, 43 );
  Result.addCampoDef('costoArranque', costoArranque);
  Result.addCampoDef('costoParada', costoParada);
  Result.addCampoDef('costoPorPasoOn', costoPorPasoOn);
  Result.addCampoDef('costoPorPasoOff', costoPorPasoOff);
end;

procedure TFichaGTer_OnOffPorPaso_ConRestricciones.BeforeRead(version,
  id_hilo: integer);
begin
  inherited BeforeRead(version, id_hilo);
end;

procedure TFichaGTer_OnOffPorPaso_ConRestricciones.AfterRead(f:TArchiTexto);
begin
  inherited AfterRead(f);
end;



procedure TFichaGTer_OnOffPorPaso_ConRestricciones.generarLineaResumen(
  var archi: TextFile);
begin
  Write(archi, FloatToStrF(PMin, formatoReales, 8, 1), #9,
    //PMín
    FloatToStrF(PMax, formatoReales, 8, 1), #9,
    //PMáx
    FloatToStrF(cv_min, formatoReales, 8, 2), #9,
    //CV_Mín
    FloatToStrF(((cv_min * PMin + cv * (PMax - PMin)) / PMax), formatoReales, 8, 2),
    #9,   //CV_Medio
    FloatToStrF(cv, formatoReales, 8, 2), #9,
    //CV_Incremental
    FloatToStrF(disp, formatoReales, 8, 2), #9,
    //FDisp
    FloatToStrF(costoArranque, formatoReales, 8, 2), #9,
    //Costo Arranque
    FloatToStrF(costoParada, formatoReales, 8, 2), #9,
    //Costo Parada
    minimoNPasosOn, #9,
    //mínNPasosOn
    minimoNPasosOff, #9,
    //mínNPasosOff
    boolToStr(decisionOnOff_PorCiclo), #9,
    //desiciónPasosOnPorCiclo
    boolToStr(decisionOffOn_PorCiclo), #9,
    //desiciónPasosOffPorCiclo
    costoPorPasoOn, #9,
    //costoPorPasoOn
    costoPorPasoOff, #9);
  //costoPorPasoOff
end;

function TFichaGTer_OnOffPorPaso_ConRestricciones.infoAd_: string;
begin
  Result := 'PMín= ' + FloatToStrF(PMin, ffGeneral, 10, 1) + ' MW, ' +
    'PMáx= ' + FloatToStrF(PMax, ffGeneral, 10, 1) + ' MW, ' +
    'cv_min= ' + FloatToStrF(cv_min, ffGeneral, 10, 1) + ' USD/MWh, ' +
    'cv= ' + FloatToStrF(cv, ffGeneral, 10, 1) + ' USD/MWh, ' + 'fDisp= ' +
    FloatToStrF(disp, ffGeneral, 10, 2) + ' p.u., ' + 'tRep= ' +
    FloatToStrF(tRepHoras, ffGeneral, 10, 1) + 'h, ' + 'mínNPasosOn= ' +
    IntToStr(minimoNPasosOn) + ', ' + 'mínNPasosOff= ' + IntToStr(minimoNPasosOff);
end;

procedure TFichaGTer_OnOffPorPaso_ConRestricciones.Free;
begin
  inherited Free;
end;








































//-----------------------------------------------
// Métodos de TGTer_OnOffPorPaso_ConRestricciones
//===============================================
procedure TGTer_OnOffPorPaso_ConRestricciones.dump_Variables(var f: TextFile;
  charIndentacion: char);
begin
  inherited dump_Variables(f, charIndentacion);
  writeln(f, charIndentacion, 'c0[USD/MWh]= ', FloatToStrF(c0, ffFixed, 10, 3));
  writeln(f, charIndentacion, 'cv[USD/MWh]= ', FloatToStrF(cv, ffFixed, 10, 3));

  writeln(f, charIndentacion, 'PMin[MW]= ', FloatToStrF(PMin, ffFixed, 10, 3));
  writeln(f, charIndentacion, 'PMAx[MW]= ', FloatToStrF(pa.PMax, ffFixed, 10, 3));
  writeln(f, charIndentacion, 'PxMax[MW]= ', FloatToStrF(PxMax, ffFixed, 10, 3));
  writeln(f, charIndentacion, 'PMaxDisponible[MW]= ',
    FloatToStrF(PMaxDisponible, ffFixed, 10, 3));

  writeln(f, charIndentacion, 'NMaquinasDisponibles= ', NMaquinasDisponibles);

  writeln(f, charIndentacion, 'HayRestrEMaxPasoDeTiempo= ',
    pa.hayRestriccionEmaxPasoDeTiempo);
  writeln(f, charIndentacion, 'EMaxPasoDeTiempo[MW/h]= ',
    FloatToStrF(pa.EmaxPasoDeTiempo, ffFixed, 10, 3));

  writeln(f);
end;

class function TGTer_OnOffPorPaso_ConRestricciones.TipoFichaLPD: TClaseDeFichaLPD;
begin
  Result := TFichaGTer_OnOffPorPaso_ConRestricciones;
end;


function TGTer_OnOffPorPaso_ConRestricciones.xTou(x: integer): integer;
begin
  // primero la encajonamos
  if x < (1 - pa.minimoNPasosOff) then
    if pa.decisionOffOn_PorCiclo then
    begin
      while x < (1 - pa.minimoNPasosOff) do
        x := x + pa.minimoNPasosOff;
    end
    else
    begin
      x := 1 - pa.minimoNPasosOff;
    end
  else
  if x > pa.minimoNPasosOn then
    if pa.decisionOnOff_PorCiclo then
    begin
      while x > pa.minimoNPasosOn do
        x := x - pa.minimoNPasosOn;
    end
    else
    begin
      x := pa.minimoNPasosOn;
    end;
  Result := x + pa.minimoNPasosOff - 1;
end;


function TGTer_OnOffPorPaso_ConRestricciones.uTox(u: integer): integer;
begin
  Result := u - pa.minimoNPasosOff + 1;
end;


{$IFDEF BOSTA}
procedure TGTer_OnOffPorPaso_ConRestricciones.AfterInstantiation;
begin
  inherited AfterInstantiation;
  pa := nil;
  nodo := nil;
end;
{$ENDIF}

constructor TGTer_OnOffPorPaso_ConRestricciones.Create(capa: integer;
  nombre: string; nacimiento, muerte: TFecha; lpdUnidades, lpd: TFichasLPD;
  nodo: TNodo; flg_CalcularGradienteDeInversion: boolean;
  estadoInicialEncendido: boolean;    //True = encendido, false = apagado
  nroPasosEnEstadoInicial: integer; TonCO2xMWh: NReal;
  LowCostMustRun, CleanDevelopmentMechanism: boolean;
  xFuenteIdxP: TFuenteAleatoria; xBorneIdxP: string );
begin
  inherited Create(capa, nombre, nacimiento, muerte, lpdUnidades, lpd, nodo,
    flg_CalcularGradienteDeInversion,
    TonCO2xMWh, LowCostMustRun, CleanDevelopmentMechanism,
    xFuenteIdxP, xBorneIdxP );
  if estadoInicialEncendido then
    self.xOnOff_AlInicio := nroPasosEnEstadoInicial
  else
    self.xOnOff_AlInicio := 1 - nroPasosEnEstadoInicial;
end;

function TGTer_OnOffPorPaso_ConRestricciones.Rec: TCosa_RecLnk;
begin
  Result:=inherited Rec;
  Result.addCampoDef('xOnOff_AlInicio', xOnOff_AlInicio);
end;

procedure TGTer_OnOffPorPaso_ConRestricciones.BeforeRead(version,
  id_hilo: integer);
begin
  inherited BeforeRead(version, id_hilo);
end;

procedure TGTer_OnOffPorPaso_ConRestricciones.AfterRead(f:TArchiTexto);
begin
  inherited AfterRead(f);
  pa := nil;
  nodo := nil;
end;


procedure TGTer_OnOffPorPaso_ConRestricciones.PrepararMemoria(
  Catalogo: TCatalogoReferencias; globs: TGlobs);
begin
  inherited prepararMemoria( Catalogo, globs );
  NMaquinasDespachadas_PasoAnterior := 0;
  NMaquinasDespachadas_EstePaso := 0;
end;

procedure TGTer_OnOffPorPaso_ConRestricciones.RegistrarParametrosDinamicos(
  CatalogoReferencias: TCatalogoReferencias);
var
  i: integer;
  ficha: TFichaGTer_OnOffPorPaso_ConRestricciones;
begin
  inherited registrarParametrosDinamicos( CatalogoReferencias );
  lpd.expandirFichas( CatalogoReferencias, globs);
  lpd.RegistrarFichasAActualizar(Self, globs.ActualizadorLPD, @pA, nil);
  for i := 0 to lpd.Count - 1 do
  begin
    ficha := TFichaGTer_OnOffPorPaso_ConRestricciones(lpd[i]);
    if ficha.indicePreciosPorCombustible <> nil then
      ficha.nroBornePreciosPorCombustible :=
        ficha.indicePreciosPorCombustible.IdBorne(ficha.bornePreciosPorCombustible);
  end;
end;

function TGTer_OnOffPorPaso_ConRestricciones.PotenciaFirme: NReal;
begin
  Result := (paUnidades.nUnidades_Operativas[0]) * pa.PMax * pa.disp;
end;

function TGTer_OnOffPorPaso_ConRestricciones.InfoAd_: string;
begin
  Result := '';
end;

class function TGTer_OnOffPorPaso_ConRestricciones.DescClase: string;
begin
  Result := rsGeneradorTermicoEYATiempoRestringido;
end;


procedure TGTer_OnOffPorPaso_ConRestricciones.PosicionarseEnEstrellita;
begin
  X_OnOff := uTox(globs.CF.xd[ixd]);

  if X_OnOff <= 0 then
    Self.NMaquinasDespachadas_PasoAnterior := 0
  else
    NMaquinasDespachadas_PasoAnterior := 1;
end;

procedure TGTer_OnOffPorPaso_ConRestricciones.ActualizarEstadoGlobal(flg_Xs: boolean);
begin
  globs.CF.xd[ixd] := xTou(X_OnOff);
end;

procedure TGTer_OnOffPorPaso_ConRestricciones.Sim_Cronica_Inicio;
begin
  inherited Sim_Cronica_Inicio;
  X_OnOff := xOnOff_AlInicio;
  if X_OnOff <= 0 then
    Self.NMaquinasDespachadas_PasoAnterior := 0
  else
    NMaquinasDespachadas_PasoAnterior := 1;
end;

function TGTer_OnOffPorPaso_ConRestricciones.get_pa_FD(kTipoUnidad: integer): NReal;
begin
  Result := pa.disp;
end;

function TGTer_OnOffPorPaso_ConRestricciones.get_pa_TMR(kTipoUnidad: integer): NReal;
begin
  Result := pa.tRepHoras;
end;



procedure TGTer_OnOffPorPaso_ConRestricciones.SorteosDelPaso(sortear: boolean);
begin

  if hayForzamientos or globs.ObligarDisponibilidad_1_ then
  begin
    NMaquinasDisponibles := paUnidades.nUnidades_Operativas[0];
    PMaxDisponible := pa.PMax * NMaquinasDisponibles;
  end
  else
  if sortear then
  begin
    ActualizarProbabilidadesReparacionYRotura_(pa.disp, pa.tRepHoras);
    NMaquinasDisponibles := Sorteos_RepRotUnidades;
    PMaxDisponible := pa.PMax * NMaquinasDisponibles;
  end
  else
  begin
    NMaquinasDisponibles := paUnidades.nUnidades_Operativas[0];
    PMaxDisponible := pa.PMax * pa.disp * NMaquinasDisponibles;
  end;
end;

procedure TGTer_OnOffPorPaso_ConRestricciones.PrepararPaso_ps;
var
  indice: NReal;
  nPasos: integer;
begin
  if X_OnOff <= 0 then
    Self.NMaquinasDespachadas_PasoAnterior := 0
  else
    NMaquinasDespachadas_PasoAnterior := 1;


  if X_OnOff <= 0 then
  begin
    nPasos := 1 - X_OnOff;
    if pa.decisionOffOn_PorCiclo then
      libre := (nPasos mod pa.minimoNPasosOff) = 0
    else
      libre := nPasos >= pa.minimoNPasosOff;

    if not libre then
      NMaquinasDisponibles := 0;

  end
  else
  begin
    nPasos := x_OnOff;
    if pa.decisionOnOff_PorCiclo then
      libre := (nPasos mod pa.minimoNPasosOn) = 0
    else
      libre := nPasos >= pa.minimoNPasosOn;
  end;


  PMin := pa.PMIn;
  PxMax := pa.PMax - pa.PMin;

  if pa.indicePreciosPorCombustible <> nil then
  begin
    indice := pa.indicePreciosPorCombustible.bornera[pa.nroBornePreciosPorCombustible];
    c0 := pa.PMin * pa.cv_min * indice;
    cv := pa.cv * indice;
  end
  else
  begin
    c0 := pa.PMin * pa.cv_min;
    cv := pa.cv;
  end;
end;

procedure TGTer_OnOffPorPaso_ConRestricciones.Sim_Paso_Fin;
var
  iposte: integer;
begin
  if NMaquinasDespachadas_EstePaso > 0 then
  begin
    for iposte := 0 to high(P) do
      potMedia_despachada := potMedia_despachada + P[iposte] * globs.durpos[iposte];
    potMedia_despachada := potMedia_despachada * globs.invHorasDelPaso;
  end
  else
  begin
    potMedia_despachada := 0;
  end;
end;

procedure TGTer_OnOffPorPaso_ConRestricciones.opt_nvers(var ivar, ivae, ires: integer);
begin
  if NMaquinasDisponibles = 0 then
    exit; // si no hay máquinas no juego

  if libre then
  begin  // si es libre, la trato como una térmica con mínimo técnico
    Self.ivar := ivar;
    ivar := ivar + globs.NPostes + 1;

    Self.ires := ires;
    ires := ires + globs.NPostes;

    Self.ivae := ivae;
    ivae := ivae + 1;
  end
  else
  if X_OnOff > 0 then
  begin
    Self.ivar := ivar;
    ivar := ivar + globs.NPostes;
  end;

  if pA.HayRestriccionEmaxPasoDeTiempo then
    Inc(ires);
end;

procedure TGTer_OnOffPorPaso_ConRestricciones.optx_nvxs(
  var ixr, ixd, iauxNReal, iauxInt: integer);
begin
  Self.ixd := ixd;
  ixd := ixd + 1;
end;

procedure TGTer_OnOffPorPaso_ConRestricciones.optx_RegistrarVariablesDeEstado(
  adminEstados: TAdminEstados);
begin
  adminEstados.Registrar_Discreta(
    ixd,
    pa.minimoNPasosOn + pa.minimoNPasosOff,
    Self.Nombre + '_xOnOff', 'pasos');
end;

procedure TGTer_OnOffPorPaso_ConRestricciones.opt_cargue(s: TSimplex);
var
  inodores: integer;
  iposte: integer;
  jres: integer;
begin
  if NMaquinasDisponibles = 0 then
    exit; // si no hay máquinas no juego

  // controlamos el uso correcto del modelo.
  if NMaquinasDisponibles > 1 then
    raise Exception.Create(
      'Por ahora TGer_onoffPorPasoConRestricciones soporta solamente centrales de una sóla máquina');

  inodores := nodo.ires;

  // aporte a las restricciones del nodo
  for iposte := 0 to globs.NPostes - 1 do
    s.pon_e(inodores + iposte, ivar + iposte, 1); // coeficiente de la B[iposte]

  // apuntamos a la primer restricción.
  jres := ires;

  if libre then
  begin
    // aporte a las restricciones del nodo
    for iposte := 0 to globs.NPostes - 1 do
      s.pon_e(inodores + iposte, ivar + globs.NPostes, Pmin); // coef A

    // restricciones adicionales impuestas por la variable de Acople
    for iposte := 0 to globs.NPostes - 1 do
    begin
      s.pon_e(jres, ivar + iposte, -1); // coef B[iposte]
      s.pon_e(jres, ivar + globs.NPostes, PxMax);
      Inc(jres);
    end;
  end
  else
  begin
    // aporte a las restricciones del nodo
    // si no esta libre y llegamos hasta aqui es que esta prendida
    for iposte := 0 to globs.NPostes - 1 do
      s.acum_e(inodores + iposte, s.nc, Pmin);
  end;


  //Restriccion a la energía máxima generable en un paso
  if pA.HayRestriccionEmaxPasoDeTiempo then
  begin
    for iposte := 0 to globs.NPostes - 1 do
      s.pon_e(jres, ivar + iposte, -globs.durpos[iposte]);
    s.pon_e(jres, s.nc, pa.EmaxPasoDeTiempo - pa.PMin * globs.HorasDelPaso);
  end;

  // aportes a la función de utilidad
  for iposte := 0 to globs.NPostes - 1 do
    s.pon_e(s.nf, ivar + iposte, -cv * globs.DurPos[iposte]);

  if libre then
    s.acum_e(s.nf, ivar + globs.nPostes, -c0 * globs.HorasDelPaso)
  else
    s.acum_e(s.nf, s.nc, -c0 * globs.HorasDelPaso);



  // si no estoy libre, no puedo tomar acciones que cambien el costo futuro
  if self.libre then
  begin
    // agregar costos de Arranque o Parada según corresponda
    if X_OnOff <= 0 then // estaba apagada
    begin
      dCF := globs.CF.deltaCosto_vxd_continuo(ixd, globs.kPaso_Opt +
        1, pa.minimoNPasosOff) * globs.fActPaso;
      s.acum_e(s.nf, ivar + globs.NPostes, -(pa.costoArranque +
        pa.costoPorPasoOn - pa.costoPorPasoOff + dCF));
      s.acum_e(s.nf, s.nc, -pa.costoPorPasoOff);
      //      writeln( 'dCF: ', dCF,  '  ', (dCF/ 160/24):12:2, ' ', (dCF/210/24):12:2, ' cmg: ', nodo.cmarg[1] );
    end
    else // estaba prendida
    begin
      dCF := globs.CF.deltaCosto_vxd_continuo(ixd, globs.kPaso_Opt +
        1, -pa.minimoNPasosOn) * globs.fActPaso;

      s.acum_e(s.nf, ivar + globs.NPostes, (pa.costoParada + dCF) -
        (pa.costoPorPasoOn - pa.costoPorPasoOff));
      s.acum_e(s.nf, s.nc, -(pa.costoParada + pa.costoPorPasoOff + dCF));
    end;
  end
  else
  begin
    // esto es inecesario, pues no afecta las decisiones del paso,
    // pero lo sumo para que sea más representativo el costo
    // resultante del simplex.
    if X_OnOff <= 0 then // estaba apagada
      s.acum_e(s.nf, s.nc, -pa.costoPorPasoOff)
    else
      s.acum_e(s.nf, s.nc, -pa.costoPorPasoOn);
  end;
end;

procedure TGTer_OnOffPorPaso_ConRestricciones.opt_fijarRestriccionesDeCaja(
  s: TSimplex);
var
  iposte: integer;
  acoples: TListaAcoplesVEntera;
begin
  if NMaquinasDisponibles = 0 then
    exit;

  if (not hayForzamientos) then
  begin
    if libre then
    begin
      // Restricciones de caja de las B (Esto no es necesario pues las restricciones adicionales
      // obligan que estas se cumplan. Por eso le pongo el 1.1 para dejarle la caja un poco floja
      for iposte := 0 to globs.NPostes - 1 do
        s.cota_sup_set(ivar + iposte, PxMax * 1.1);

      setLength(acoples, globs.NPostes);
      for iposte := 0 to globs.NPostes - 1 do
      begin
        acoples[iposte].ivar := ivar + iposte;
        acoples[iposte].ires := ires + iposte;
      end;
      // Restricciones de caja de las A y las declaramos enteras
      TMIPSimplex(s).set_EnteraConAcoples(
        ivae, ivar + globs.NPostes, NMaquinasDisponibles, acoples);
    end
    else
    begin
      for iposte := 0 to globs.NPostes - 1 do
        s.cota_sup_set(ivar + iposte, PxMax);
    end;
  end
  else
  begin
    if libre then
    begin
      // Restricciones de caja de las B (Esto no es necesario pues las restricciones adicionales
      // obligan que estas se cumplan. Por eso le pongo el 1.1 para dejarle la caja un poco floja
      for iposte := 0 to globs.NPostes - 1 do
        s.FijarVariable(ivar + iposte, paForzamiento.P[0] - pa.Pmin);

      setLength(acoples, globs.NPostes);
      for iposte := 0 to globs.NPostes - 1 do
      begin
        acoples[iposte].ivar := ivar + iposte;
        acoples[iposte].ires := ires + iposte;
      end;
      // Restricciones de caja de las A y las declaramos enteras
      TMIPSimplex(s).set_EnteraConAcoples(
        ivae, ivar + globs.NPostes, NMaquinasDisponibles, acoples);
      s.FijarVariable(ivar + globs.NPostes, NMaquinasDisponibles);
    end
    else
    begin
      for iposte := 0 to globs.NPostes - 1 do
        s.FijarVariable(ivar + iposte, paForzamiento.P[0] - pa.Pmin);
    end;
  end;
end;

procedure TGTer_OnOffPorPaso_ConRestricciones.opt_leerSolucion(s: TSimplex);
var
  iposte: integer;
  dP: NReal;
begin
  CostoVariableDelPaso := 0;
  CostoFijoDelPaso := 0;

  if NMaquinasDisponibles = 0 then
  begin
    vclear(P);
    //vclear( cv_Spot );
    for iposte := 0 to globs.NPostes - 1 do
    begin
      Lambda_P[iPoste]:= 0;
      cv_Spot[iPoste]:=get_cv_falla; // para que en el ordenamiento quede último, con costo variable igual a la falla;
    end;
    NMaquinasDespachadas_EstePaso := 0;
  end
  else
  begin
    if libre then
      NMaquinasDespachadas_EstePaso := trunc(s.xval(ivar + globs.NPostes) + 0.2)
    else
      NMaquinasDespachadas_EstePaso := 1;

    if NMaquinasDespachadas_EstePaso > 0 then
    begin
      for iposte := 0 to globs.NPostes - 1 do
      begin
        dP := s.xval(ivar + iposte);
        P[iposte] := dP + Pmin;
        Lambda_P[iPoste]:= s.xmult( ires + iposte ) / globs.DurPos[iposte];
        cv_Spot[iPoste]:= Nodo.cmarg[iposte] - Lambda_P[iPoste];
        CostoVariableDelPaso := CostoVariableDelPaso + dP * cv * globs.DurPos[iposte];
      end;
      CostoVariableDelPaso := CostoVariableDelPaso + c0 *
        globs.HorasDelPaso * NMaquinasDespachadas_EstePaso;
    end
    else
    begin
      vclear(P);
    end;
  end;

  if NMaquinasDespachadas_EstePaso = NMaquinasDespachadas_PasoAnterior then
  begin
    nMaquinasArrancadas := 0;
    // no cambié el estado de arranque o parada.
    if X_OnOff > 0 then
    begin
      Xs_OnOff := X_OnOff + 1;
      CostoFijoDelPaso := pa.costoPorPasoOn;
    end
    else
    begin
      Xs_OnOff := X_OnOff - 1;
      CostoFijoDelPaso := pa.costoPorPasoOff;
    end;
  end
  else
  begin
    // invertí el estado de arranque o parada
    if X_OnOff > 0 then
    begin
      Xs_OnOff := 0;
      nMaquinasArrancadas := -1;
      CostoFijoDelPaso := pa.costoPorPasoOff + pa.costoParada;
    end
    else
    begin
      Xs_OnOff := 1;
      nMaquinasArrancadas := 1;
      CostoFijoDelPaso := pa.costoPorPasoOn + pa.costoArranque;
    end;
  end;

  CostoDirectoDelPaso := CostoVariableDelPaso + CostoFijoDelPaso;

end;

procedure TGTer_OnOffPorPaso_ConRestricciones.EvolucionarEstado;
begin
  X_OnOff := Xs_OnOff;
  NMaquinasDespachadas_PasoAnterior := NMaquinasDespachadas_EstePaso;
end;


function TGTer_OnOffPorPaso_ConRestricciones.getNombreVar(ivar: integer;
  var nombre: string): boolean;
begin
  if NMaquinasDisponibles = 0 then // si no hay máquinas no juego
    Result := False
  else
  if (ivar >= self.ivar) and (ivar < self.ivar + globs.NPostes) then
  begin
    nombre := self.Nombre + '_P[MW]' + IntToStr(ivar - self.ivar + 1);
    Result := True;
  end
  else if (ivar = self.ivar + globs.NPostes) then
  begin
    nombre := self.Nombre + '_A[MW]';
    Result := True;
  end
  else
    Result := False;
end;

function TGTer_OnOffPorPaso_ConRestricciones.getNombreRes(ires: integer;
  var nombre: string): boolean;
begin
  if NMaquinasDisponibles = 0 then // si no hay máquinas no juego
    Result := False
  else
  if (ires >= self.ires) and (ires < self.ires + globs.NPostes) then
  begin
    nombre := self.nombre + '_res-A' + IntToStr(ires - self.ires + 1);
    Result := True;
  end
  else if pa.HayRestriccionEmaxPasoDeTiempo and (ires = self.ires + globs.NPostes) then
  begin
    nombre := self.nombre + '_res-EMax';
    Result := True;
  end
  else
    Result := False;
end;

procedure TGTer_OnOffPorPaso_ConRestricciones.PubliVars;
begin
  inherited PubliVars;
  PublicarVariableNR('CostoVariable', '[USD]', 12, 1, CostoVariableDelPaso, True);
  PublicarVariableNR('CostoAPP', '[USD]', 12, 1, CostoFijoDelPaso, True);
  PublicarVariableNI('NMaqsDespachadas_EstePaso', '-',
    NMaquinasDespachadas_EstePaso, True);
  PublicarVariableNI('NMaqsDespachadas_PasoAnterior', '-',
    NMaquinasDespachadas_PasoAnterior, False);
  PublicarVariableNR('c0', '[USD/h]', 6, 1, c0, False);
  PublicarVariableNI('NMaqsDisponibles', '-', NMaquinasDisponibles, False);
  PublicarVariableNR('PMaxDisponible', '[MW]', 6, 1, PMaxDisponible, False);
  PublicarVariableNR('PMediaDespachada', '[MW]', 6, 1, potMedia_despachada, False);
end;

procedure TGTer_OnOffPorPaso_ConRestricciones.Free;
begin
  inherited Free;
end;

procedure AlInicio;
begin
  registrarClaseDeCosa(TGTer_OnOffPorPaso_ConRestricciones.ClassName,
    TGTer_OnOffPorPaso_ConRestricciones);
  registrarClaseDeCosa(TFichaGTer_OnOffPorPaso_ConRestricciones.ClassName,
    TFichaGTer_OnOffPorPaso_ConRestricciones);
end;

procedure AlFinal;
begin
end;

end.
