unit ubancodebaterias01;

interface

uses
  Classes,
  Math,
  xMatDefs, uSimplex,
  ucosa, uCosaConNombre,
  uGlobs, uFechas,
  uGeneradores,
  uNodos,
  SysUtils,
  uEstados,
  uFichasLPD,
  uFuentesAleatorias,
  uconstantesSimSEE,
  uevapUruguay;

resourcestring
  rsBancoDeBaterias01 = 'Banco de Baterías';

type
  TBancoDeBaterias01 = class; // forward declaration



  { TFichaBancoDeBaterias01 }

  TFichaBancoDeBaterias01 = class(TFichaLPD)
  public
    PMax_Gen: NReal;
    PMax_Dem: NReal;
    ren_Gen: NReal;
    ren_Dem: NReal;
    CapacidadMaxima_MWh: NReal;
    fd: NReal;
    tmr: NReal;

    EnsayoCiclado_pd1: NReal;
    EnsayoCiclado_nc1: integer;
    EnsayoCiclado_pd2: NReal;
    EnsayoCiclado_nc2: integer;

    AniosEnvejecimientoNatural_ELIMINADO_: integer;//no se usa para nada, borrar
    //CapacidadFinal_pu: NReal;

    CostoRecambio_USD_kWh_instalado: NReal;
    AniosRecambio_costeo: integer;
    TasaDescuento_costeo: NReal;

    cv_MWh_ValorizadoManual: NReal;
    pagoPorDisponibilidad_USDxMWhh: NReal;
    (**************************************************************************)

    constructor Create(capa: integer; fecha: TFecha; periodicidad: TPeriodicidad;
      PMax_Gen, PMax_Dem: NReal; ren_Gen, ren_Dem: NReal;
      CapacidadMaxima_MWh: NReal;
      fd, TMR, cv_MWh_ValorizadoManual, pagoPorDisponibilidad_USDxMWhh,
      EnsayoCiclado_pd1: NReal; EnsayoCiclado_nc1: integer;
      EnsayoCiclado_pd2: NReal; EnsayoCiclado_nc2: integer;
      CostoRecambio_USD_kWh_instalado: NReal; AniosRecambio_costeo: integer;
      TasaDescuento_costeo: NReal);

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

  end;




  { TBancoDeBaterias01 }

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

    //Variable de Estado
    NDisc: integer; // discretizacion del estad
    Carga_ini: NReal; // valor inicial
    flg_ValorizadoManual: boolean;
    flg_AplicarRestriccionesHorariasIterando: boolean;
    (**************************************************************************)


    pa: TFichaBancoDeBaterias01;

    PMax_Gen_PorPoste: TDAOfNReal;
    PMax_Dem_PorPoste: TDAOfNReal;

    // auxiliares para imposición de restricciones por poste
    X_Carga_min, X_Carga_max: TDAOfNReal;



    X_Carga: NReal;
    Xs_Carga: NReal;

    cv_MWh: NReal; // valor de la energía almacenada

    cvDegDem, cvDegGen: NReal; // costos variables por degradación
    // estimados en un paso de tiempo actuando como Demanda o como Generador.

    // derivada del costo futuro respecto a la carga de la bateria
    dCFdC: NReal;

    NMaquinasDisponibles: integer;


    CapacidadInstalada_MWh: NReal;
    PMax_Gen, PMax_Dem: NReal;
    PGen, PDem: TDAOfNReal;


    flg_UsarCostoDegradacionRecambio: boolean;
    // al cambiar la ficha dinámica se actualiza
    alfa, beta: NReal;
    expmalfadiv2, expmalfadiv2porbeta: NReal; // auxiliar

    cvRecambio_USD: NReal; // Porción del costo de recambio que se degrada por uso
    // correspondiente a la CapacidadInstalada

    PorcionFija: NReal;  // Valor en p.u. que determinal la Porción del costo
    // de recambio que se degrada independientemente del uso
    cfRecambio_USD_MWh_instalado_PorPasoT: NReal; // USD/MW-instalado que se degrada
    // en cada paso de tiempo independientemente del uso
    cvRecambio_USD_MWh_instalado: Nreal; // USD/MW-instalado asociado a la degradación
    // variable.



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

    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(Catalogo: TCatalogoReferencias); override;


    function PotenciaFirme: NReal; override;

    function InfoAd_: string; override;

    class function DescClase: string; override;


    //  procedure Sim_Inicio; 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;

    // Porción de Vida perdida por pasar el estado de Carga1 al estado de Carga2
    // la funcion controla que los estados de carga estén en los rangos adecuados.
    function PorcionDegradada(Carga1_MWh, Carga2_MWh: NReal): NReal;

    procedure PrepararPaso_ps; override;

    function opt_necesitoIterar(kIteracion: integer;
      var errRelativo: NReal): boolean;
      override;


    procedure opt_nvers(var ivar, ivae, ires: integer); override;

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

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

    procedure CambioFichaPD; override;


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

    procedure PosicionarseEnEstrellita; override;

    procedure ActualizarEstadoGlobal(flg_Xs: boolean); override;

    procedure optx_nvxs(var ixr, ixd, iauxr, iauxd: integer); override;
    procedure optx_RegistrarVariablesDeEstado(adminEstados: TAdminEstados);
      override;

    destructor Destroy; override;
    procedure PubliVars; override;

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


    procedure InicioSim; override;
    procedure InicioOpt; override;

  end;

procedure TBancoDeBaterias01_cambioFichaPD(Actor: TCosa);

procedure AlInicio;
procedure AlFinal;

implementation


constructor TBancoDeBaterias01.Create(capa: integer; nombre: string;
  nacimiento, muerte: TFecha; lpdUnidades, lpd: TFichasLPD; nodo: TNodo;
  flg_CalcularGradienteDeInversion: boolean; Carga_ini: NReal;
  NDisc_: integer; flg_ValorizadoManual,
  flg_AplicarRestriccionesHorariasIterando: boolean;
  TonCO2xMWh: NReal; LowCostMustRun, CleanDevelopmentMechanism: boolean;
  xFuenteIdxP: TFuenteAleatoria; xBorneIdxP: string);

begin
  inherited Create(capa, nombre, nacimiento, muerte, lpdUnidades, nodo,
    TonCO2xMWh, LowCostMustRun, CleanDevelopmentMechanism,
    flg_CalcularGradienteDeInversion,
    xFuenteIdxP, xBorneIdxP);
  Self.lpd := lpd;

  self.Carga_ini := Carga_ini;
  self.NDisc := NDisc_;
  self.flg_ValorizadoManual := flg_ValorizadoManual;
  self.flg_AplicarRestriccionesHorariasIterando :=
    flg_AplicarRestriccionesHorariasIterando;
end;

function TBancoDeBaterias01.Rec: TCosa_RecLnk;
begin
  Result := inherited Rec;
  Result.addCampoDef('Carga_ini', Carga_Ini);
  Result.addCampoDef('NDisc', NDisc);
  Result.addCampoDef('flg_ValorizadoManual', flg_ValorizadoManual, 121);
  Result.addCampoDef('flg_AplicarRestriccionesHorariasIterando',
    flg_AplicarRestriccionesHorariasIterando, 195, 0, 'T');
  Result.addCampoDef('lpd', TCosa(lpd));
end;

procedure TBancoDeBaterias01.BeforeRead(version, id_hilo: integer);
begin
  inherited BeforeRead(version, id_hilo);
  lpd := nil;
end;

procedure TBancoDeBaterias01.AfterRead(f: TArchiTexto);
begin
  inherited AfterRead(f);
  pa := nil;
  nodo := nil;
  if lpd <> nil then
    lpd.Propietario := self;

end;


procedure TBancoDeBaterias01.PrepararMemoria(Catalogo: TCatalogoReferencias;
  globs: TGlobs);
begin
  inherited prepararMemoria(Catalogo, globs);
  setlength(PGen, globs.NPostes);
  setlength(PDem, globs.NPostes);

  setlength(PMax_Gen_PorPoste, globs.NPostes);
  setlength(PMax_Dem_PorPoste, globs.NPostes);

  setlength(X_Carga_min, globs.NPostes);
  setlength(X_Carga_max, globs.NPostes);
end;



procedure TBancoDeBaterias01.RegistrarParametrosDinamicos(
  Catalogo: TCatalogoReferencias);
begin
  inherited registrarParametrosDinamicos(Catalogo);
  lpd.expandirFichas(Catalogo, globs);
  lpd.RegistrarFichasAActualizar(Self, globs.ActualizadorLPD, @pa,
    nil, TBancoDeBaterias01_cambioFichaPD);
end;

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

function TBancoDeBaterias01.PotenciaFirme: NReal;
begin
  Result := 0;
end;

class function TBancoDeBaterias01.DescClase: string;
begin
  Result := rsBancoDeBaterias01;
end;

function TBancoDeBaterias01.get_pa_FD(kTipoUnidad: integer): NReal;
begin
  Result := pa.fd;
end;

function TBancoDeBaterias01.get_pa_TMR(kTipoUnidad: integer): NReal;
begin
  Result := pa.tmr;
end;



procedure TBancoDeBaterias01.SorteosDelPaso(sortear: boolean);
begin
  CapacidadInstalada_MWh := paUnidades.nUnidades_Instaladas[0] * pa.CapacidadMaxima_MWh;

  if globs.ObligarDisponibilidad_1_ or (not sortear) then
    NMaquinasDisponibles := paUnidades.nUnidades_Operativas[0]
  else
  begin
    ActualizarProbabilidadesReparacionYRotura_(pa.fd, pa.tmr);
    NMaquinasDisponibles := Sorteos_RepRotUnidades;
  end;
  PMax_Gen := pa.PMax_Gen * NMaquinasDisponibles;
  PMax_Dem := pa.PMax_Dem * NMaquinasDisponibles;

end;

function TBancoDeBaterias01.PorcionDegradada(Carga1_MWh, Carga2_MWh: NReal): NReal;
var
  u, us, u_alaBeta, us_alaBeta: NReal;
begin
  if Carga1_MWh < 0 then
    Carga1_MWh := 0;
  if Carga2_MWh < 0 then
    Carga2_MWh := 0;

  u := (CapacidadInstalada_MWh - Carga1_MWh) / CapacidadInstalada_MWh;
  if u > AsumaCero then
    U_alaBeta := power(u, beta)
  else
    U_alaBeta := 0;

  us := (CapacidadInstalada_MWh - Carga2_MWh) / CapacidadInstalada_MWh;
  if us > AsumaCero then
    Us_alaBeta := power(us, beta)
  else
    Us_alaBeta := 0;
  Result := expmalfadiv2 * abs(U_alaBeta - Us_alaBeta);
end;


procedure TBancoDeBaterias01.dump_Variables(var f: TextFile; charIndentacion: char);
begin
  inherited dump_Variables(f, charIndentacion);
  writeln(f, charIndentacion, 'Carga[MWh]= ', FloatToStrF(X_Carga, ffFixed, 10, 3));
  writeln(f);
end;

procedure TBancoDeBaterias01.InicioSim;
begin
  inherited InicioSim;
end;

procedure TBancoDeBaterias01.InicioOpt;
begin
  inherited InicioOpt;
end;

procedure TBancoDeBaterias01.PrepararPaso_ps;
var
  res: integer;
  xrpos: NREal;
  cv_Dem, cv_Gen: NReal;
  X_Carga_Proyectado, dCarga, dP: NReal;
  kPoste, kHora: integer;

begin
  if X_Carga > CapacidadInstalada_MWh then
  begin  // Si pasa esto quiere decir que se dieron de baja baterías.
    X_Carga := 0.99 * CapacidadInstalada_MWh;
    Xs_Carga := X_Carga;
  end;

  if flg_ValorizadoManual then
    cv_MWh := pa.cv_MWh_ValorizadoManual
  else
  begin
    globs.CF.devxr_continuo(
      ixr, globs.kPaso_Opt + 1, cv_Dem, cv_Gen, res, xrpos);

    case res of
      -1: cv_MWh := cv_Dem;
      0: cv_MWh := (cv_Dem + cv_Gen) / 2.0;
      1: cv_MWh := cv_Gen;
    end;

    if CapacidadInstalada_MWh > 0.00001 then
      cv_MWh := -cv_MWh / CapacidadInstalada_MWh;
  end;

  if flg_UsarCostoDegradacionRecambio then
  begin

    if fuenteInidicePrecios <> nil then
      cvRecambio_USD := CapacidadInstalada_MWh *
        cvRecambio_USD_MWh_instalado * fuenteInidicePrecios.Bornera[kBorneIndicePrecios]
    else
      cvRecambio_USD := CapacidadInstalada_MWh * cvRecambio_USD_MWh_instalado;

    // Calculo de los costos variables por degradación como
    // aproximación lineal en el paso suponiendo que se carga o descarga
    // a PMaxDem o PMaxGen.
    // Carga
    if PMax_Dem > AsumaCero then
    begin
      X_Carga_Proyectado := min(X_Carga + PMax_Dem * globs.HorasDelPaso *
        pa.ren_Dem, CapacidadInstalada_MWh);
      dCarga := X_Carga_Proyectado - X_Carga;
      if dCarga > AsumaCero then
        cvDegDem := PorcionDegradada(X_Carga, X_Carga_Proyectado) *
          cvRecambio_USD / dCarga
      else
        cvDegDem := 0;
    end
    else
      cvDegDem := 0;

    if PMax_Gen > AsumaCero then
    begin
      X_Carga_Proyectado := max(X_Carga - PMax_Gen * globs.HorasDelPaso /
        pa.ren_Gen, 0);
      dCarga := X_Carga - X_Carga_Proyectado;
      if dCarga > AsumaCero then
        cvDegGen := PorcionDegradada(X_Carga, X_Carga_Proyectado) *
          cvRecambio_USD / dCarga
      else
        cvDegGen := 0;
    end;
  end;

  if globs.NPostes > 0 then
  begin
    if globs.cntIteracionesDelPaso <= 1 then
    begin
      // para la primer iteración ponemos la potencia máxima
      for kPoste := 0 to globs.NPostes - 1 do
      begin
        PMax_Dem_PorPoste[kPoste] := PMax_Dem;
        PMax_Gen_PorPoste[kPoste] := PMax_Gen;
      end;
    end;
  end
  else
  begin
    PMax_Dem_PorPoste[0] := PMax_Dem;
    PMax_Gen_PorPoste[0] := PMax_Gen;
  end;
end;




(***** ITERACION METODO ESTRICTO HORARIO *****
function TBancoDeBaterias01.opt_necesitoIterar(kIteracion: integer;
  var errRelativo: NReal): boolean;

var
  kPoste, kHora: integer;
  X_Carga_Proyectado: NReal;
  flg_HayQueIterar: boolean;
  s: TSimplex;

  nHoras: integer;

  sal: textfile;
  X_Carga_Corregida: NReal;

begin
  if ( globs.NPostes = 1) or ( kIteracion > 2)
  or ( not flg_AplicarRestriccionesHorariasIterando )
  or ( CapacidadInstalada_MWh < 0.0001 )
  or ( NMaquinasDisponibles = 0 ) then
  begin
    Result := False;
    exit;
  end;
{*******************
  Si NPostes > 1 iteramos para controlar que durante las horas del paso,
  la evolución de la carga sea compatible con las potencias PGen y PDem
  de los postes.
  Para ello hacemos un seguimiento desde la hora de inicio del paso chequeando
  que no se superen los límites 0 <= X_Carga <= CapacidadInstalada
  En la hora en que se supere
***}


  flg_HayQueIterar := False;

  // bien ahora chequeamos si hubo desborde de carga por la simplificación de los postes
  X_Carga_Proyectado := X_Carga;

  nHoras := length( globs.kPosteHorasDelPaso );

  for kHora := 0 to high( globs.kPosteHorasDelPaso) do
  begin
    kPoste := globs.kPosteHorasDelPaso[kHora];
    X_Carga_Proyectado := X_Carga_Proyectado + PDem[kPoste] *
      pa.ren_Dem - PGen[kPoste] / pa.ren_Gen;
    if (X_Carga_Proyectado < 0) or (X_Carga_Proyectado >
      CapacidadInstalada_MWh) then
    begin
      flg_HayQueIterar := True;
      break;
    end;
  end;

  if not flg_HayQueIterar then
  begin
    Result := False;
    exit;
  end;



  //*** NOs planteamos el problema de optmización para fijar las nuevas restricciones de caja
   s := TSimplex.Create_init(nHoras + 1, 2 * globs.NPostes + nHoras + 1, nil, nil);

  // Suponemos las variables de control (columnas) ordenadas así:
  // de 1..NPostes pg_k
  // de NPostes+1 .. NPostes+NPostes pd_k
  // de 2Npostes+1 .. 2NPostes+nHoras X_{h+1}
  // las nHoras filas del simplex representan la restricción dinámica en cada una de las nHoras del paso
  // de tiempo.


  // Cargamos las restricciones dinámicas.
  // la primera por separado pues tiene la condición incial de la Batería.
  kHora := 1;
  s.pon_e(kHora, 2 * globs.NPostes + kHora, -1.0); // -X_1
  kPoste := globs.kPosteHorasDelPaso[kHora-1];
  s.pon_e(kHora, kPoste+1, -1.0 / pa.ren_Gen);
  s.pon_e(khora, globs.NPostes + kPoste+1, pa.ren_Dem);

  // cargamos el estado inicial
  s.pon_e(kHora, s.nc, min(  max( X_Carga, CapacidadInstalada_MWh / 1000.0), ( CapacidadInstalada_MWh * ( 1.0 - 1.0/ 1000.0 ) )) );

  // arhoa cargamos el resto de las restricciones dinámicas
  for kHora := 2 to nHoras do
  begin
    s.pon_e(kHora, 2 * globs.NPostes + kHora - 1, 1.0); // X_inicio
    s.pon_e(kHora, 2 * globs.NPostes + kHora, -1.0); // -X_final
    kPoste := globs.kPosteHorasDelPaso[kHora-1];
    s.pon_e(kHora, kPoste+1, -1.0 / pa.ren_Gen); // Energía extraída en la hora
    s.pon_e(khora, globs.NPostes + kPoste+1, pa.ren_Dem); // Energía almacenada en la hora
  end;

  // Cargamos la función a maximizar
  for kPoste := 1 to globs.NPostes do
  begin
    s.pon_e(s.nf, kPoste, (nodo.cmarg[kPoste - 1] - cv_MWh / pa.ren_Gen) * globs.DurPos[kPoste - 1]);
    s.pon_e(s.nf, globs.NPostes + kPoste, (cv_MWh * pa.ren_Dem - nodo.cmarg[kPoste - 1]) *
      globs.DurPos[kPoste - 1]);
  end;

  // Restricciones de Caja.
  // A las potencias las acotamos entre 0 y el resultado de la solución leída del paso
  for kPoste := 1 to globs.NPostes do
  begin
    if PGen[kPoste - 1] > AsumaCero then
      s.cota_sup_set(kPoste, PGen[kPoste - 1])
    else
      s.FijarVariable( kPoste, 0 );

    if PDem[kPoste - 1] > AsumaCero then
      s.cota_sup_set(globs.NPostes + kPoste, PDem[kPoste - 1])
    else
      s.FijarVariable(globs.NPostes + kPoste, 0 );
    {
      s.cota_sup_set(kPoste, PGen[kPoste - 1]);
      s.cota_sup_set(globs.NPostes + kPoste, PDem[kPoste - 1]);
    }
  end;

  // Ahora fijamos el techo de la capacidad en los valores horarios.
  for kHora := 1 to nHoras do
    s.cota_sup_set(2 * globs.NPostes + kHora, CapacidadInstalada_MWh);

  // indicamos que las restricciones son de igualdad
  for kHora:= 1 to nHoras do
   s.FijarRestriccionIgualdad( kHora );

  writeln( 'Intera', globs.kPaso_Opt, ', kEstrella: ', globs.CF.ordinalEstrellaActual );

  if ( globs.kPaso_Opt = 1480 ) and ( globs.CF.ordinalEstrellaActual = 25 ) then
  begin
    s.DumpSistemaToXLT_('Simplex_IteracionBancoBat.xlt', '');
  end;

  if s.resolver < 0 then
  begin
    s.DumpSistemaToXLT_('Simplex_IteracionBancoBat.xlt', '');
    raise Exception.Create(
      'Error, no fue posible resolver el Simplex para la iteración del BancoDeBaterías');
  end;

  // Bien ahora modificamos los máximos de potencia por Poste para PGen y PDem para que se apliquen
  // en la próxima iteración.
  for kPoste := 1 to globs.NPostes do
  begin
    PMax_Gen_PorPoste[kPoste - 1] := s.xval(kPoste);
    PMax_Dem_PorPoste[kPoste - 1] := s.xval(globs.NPostes + kPoste);
  end;

  {
  assignFile( sal, 'bancobat_ajustehorario.xlt' );
  rewrite( sal );
  writeln( sal, 'hora', #9, 'poste', #9, 'X', #9,'Xc', #9, 'Techo'  );
  X_Carga_Proyectado:= X_Carga;
  X_Carga_Corregida:= X_Carga;
  writeln( sal, 0, #9, 0, #9, X_Carga_Proyectado, #9, X_Carga_Corregida, #9, CapacidadInstalada_MWh );
  for kHora:= 1 to nHoras do
  begin
    kPoste:= globs.kPosteHorasDelPaso[ kHora - 1 ];
    X_Carga_Proyectado:= X_Carga_Proyectado + PDem[kPoste]* pa.ren_Dem - PGen[kPoste] / pa.ren_Gen;
    X_Carga_Corregida:= X_Carga_Corregida + PMax_Dem_PorPoste[kPoste]* pa.ren_Dem - PMax_Gen_PorPoste[kPoste] / pa.ren_Gen;
    writeln( sal, khora, #9, kPoste+1, #9, X_Carga_Proyectado, #9, X_Carga_Corregida, #9, CapacidadInstalada_MWh );
  end;
  closefile( sal );
  }
  s.Free( true );



  errRelativo:= 1.0;
  Result := True;
end;
   ***)



function TBancoDeBaterias01.opt_necesitoIterar(kIteracion: integer;
  var errRelativo: NReal): boolean;

var
  kPoste, kHora: integer;
  X_Carga_Proyectado: NReal;
  flg_HayQueIterar: boolean;

  edp, egp: TDAOfNReal;
  eh: NReal;
  (*
  sal: textfile;
  X_Carga_Corregida: NReal;
    *)
begin
  if (globs.NPostes = 1) or (kIteracion > 2) or
    (not flg_AplicarRestriccionesHorariasIterando) or
    (CapacidadInstalada_MWh < 0.0001) or (NMaquinasDisponibles = 0) then
  begin
    Result := False;
    exit;
  end;
   {*******************
     Si NPostes > 1 iteramos para controlar que durante las horas del paso,
     la evolución de la carga sea compatible con las potencias PGen y PDem
     de los postes.
     Para ello hacemos un seguimiento desde la hora de inicio del paso chequeando
     que no se superen los límites 0 <= X_Carga <= CapacidadInstalada
     En la hora en que se supere
   ***}


  // vector con las potencias por poste
  setlength(egp, globs.NPostes);
  setlength(edp, globs.NPostes);


  flg_HayQueIterar := False;
  X_Carga_Proyectado := X_Carga;
  for kHora := 0 to high(globs.kPosteHorasDelPaso) do
  begin
    // Poste correspondiente a la hora
    kPoste := globs.kPosteHorasDelPaso[kHora];

    // Energía entrante/saliente a la Batería en la hora
    eh := PDem[kPoste] * pa.ren_Dem - PGen[kPoste] / pa.ren_Gen;

    // Estado de carga proyectado
    X_Carga_Proyectado := X_Carga_Proyectado + eh;

    if eh < 0 then
    begin // descargando la batería. Opera como Generador
      if (X_Carga_Proyectado < 0) then    // implica eh < 0 y está generando
      begin
        flg_HayQueIterar := True;
        eh := eh - X_Carga_Proyectado; // reduzco abs( eh )
        X_Carga_Proyectado := 0; // limitamos la carga al mínimo
      end;
      egp[kPoste] := egp[kPoste] - eh;
    end
    else
    begin // Cargando la batería. Opera como Demanda
      if (X_Carga_Proyectado > CapacidadInstalada_MWh) then
      begin
        flg_HayQueIterar := True;
        eh := eh - CapacidadInstalada_MWh + X_Carga_Proyectado; // reduzco valor eh
        X_Carga_Proyectado := CapacidadInstalada_MWh; // limitamos la carga al máximo
      end;
      edp[kPoste] := edp[kPoste] + eh;
    end;
  end;

  if not flg_HayQueIterar then
  begin
    Result := False;
    exit;
  end;


  // Bien ahora modificamos los máximos de potencia por Poste para PGen y PDem para que se apliquen
  // en la próxima iteración.
  for kPoste := 0 to globs.NPostes - 1 do
  begin
    PMax_Gen_PorPoste[kPoste] := egp[kPoste] / globs.DurPos[kPoste] * pa.ren_Gen;
    PMax_Dem_PorPoste[kPoste] := edp[kPoste] / globs.DurPos[kPoste] / pa.ren_Dem;
  end;

  setlength(egp, 0);
  setlength(edp, 0);
 {
     assignFile( sal, 'bancobat_ajustehorario.xlt' );
     rewrite( sal );
     writeln( sal, 'hora', #9, 'poste', #9, 'X', #9,'Xc', #9, 'Techo'  );
     X_Carga_Proyectado:= X_Carga;
     X_Carga_Corregida:= X_Carga;
     writeln( sal, 0, #9, 0, #9, X_Carga_Proyectado, #9, X_Carga_Corregida, #9, CapacidadInstalada_MWh );
     for kHora:= 0 to high(globs.kPosteHorasDelPaso)
     begin
       kPoste:= globs.kPosteHorasDelPaso[ kHora ];
       X_Carga_Proyectado:= X_Carga_Proyectado + PDem[kPoste]* pa.ren_Dem - PGen[kPoste] / pa.ren_Gen;
       X_Carga_Corregida:= X_Carga_Corregida + PMax_Dem_PorPoste[kPoste]* pa.ren_Dem - PMax_Gen_PorPoste[kPoste] / pa.ren_Gen;
       writeln( sal, khora+1, #9, kPoste+1, #9, X_Carga_Proyectado, #9, X_Carga_Corregida, #9, CapacidadInstalada_MWh );
     end;
     closefile( sal );

}

  errRelativo := 1.0;
  Result := True;
end;


procedure TBancoDeBaterias01.opt_nvers(var ivar, ivae, ires: integer);
begin
  self.ivar := ivar;
  self.ires := ires;
  if NMaquinasDisponibles > 0 then
  begin
    ivar := ivar + 2 * globs.NPostes + 1; // PGen_i ;  PDem_i ; Xs_Carga
    ires := ires + 1; // Restricción dinámica
    // X_Carga - PGen_i*dupos_i * renGen + PDem_i * dupos_i / renDem  - Xs_Carga = 0
  end;
end;

procedure TBancoDeBaterias01.opt_cargue(s: TSimplex);
var
  ibaseres: integer;
  iposte: integer;
begin

  if NMaquinasDisponibles <= 0 then
    exit;

  ibaseres := nodo.ires;

  // Tenemos que aportar a las restricciones de demanda del nodo al que está
  // conectado el generador
  for iposte := 0 to globs.NPostes - 1 do
  begin
    s.pon_e(ibaseres + iposte, ivar + iposte, 1);
    s.pon_e(ibaseres + iposte, ivar + globs.NPostes + iposte, -1);
  end;


  // Xs_Carga = X_Carga -  PGen_i * durpos_i / renGen +  PDem_i * durpos_i * renDem
  for iposte := 0 to globs.NPostes - 1 do
  begin
    s.pon_e(ires, ivar + iposte, -globs.durpos[iposte] / pa.ren_Gen);
    s.pon_e(ires, ivar + globs.NPostes + iposte, globs.durpos[iposte] * pa.ren_Dem);
  end;

  s.pon_e(ires, ivar + 2 * globs.NPostes, -1); // -Xs_Carga
  s.pon_e(ires, s.nc, X_Carga);

  // Aportes a la función de -costo.
  for iposte := 0 to globs.NPostes - 1 do
  begin
    s.pon_e(s.nf, ivar + iposte,
      s.e(ires, ivar + iposte) * (cv_MWh + cvDegGen));

    s.pon_e(s.nf, ivar + globs.NPostes + iposte, s.e(
      ires, ivar + globs.NPostes + iposte) * (cv_MWh - cvDegDem));
  end;

end;

procedure TBancoDeBaterias01.opt_fijarRestriccionesDeCaja(s: TSimplex);
var
  iposte: integer;
begin
  // Le fijamos como cota máxima PMax a la potencia en todos los postes
  if NMaquinasDisponibles > 0 then
  begin
    for iposte := 0 to globs.NPostes - 1 do
      s.cota_sup_set(ivar + iposte, PMax_Gen_PorPoste[iposte]);

    for iposte := 0 to globs.NPostes - 1 do
      s.cota_sup_set(ivar + globs.NPostes + iposte, PMax_Dem_PorPoste[iposte]);

    s.cota_sup_set(ivar + 2 * globs.NPostes, CapacidadInstalada_MWh);
    s.FijarRestriccionIgualdad(ires);
  end;
end;

procedure TBancoDeBaterias01.Sim_Cronica_Inicio;
begin
  inherited Sim_Cronica_Inicio;
  X_Carga := Carga_ini;
end;

procedure TBancoDeBaterias01.opt_leerSolucion(s: TSimplex);
var
  iposte: integer;
  CostoDegradacionVariable: NReal;

begin

  CostoDirectoDelPaso := cfRecambio_USD_MWh_instalado_PorPasoT * CapacidadInstalada_MWh;

  if NMaquinasDisponibles <= 0 then
  begin
    vclear(PGen);
    vclear(PDem);
    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;
    Ingreso_PorDisponibilidad_ := 0;
    Xs_Carga := X_Carga; // ??? ojo falta la descarga vegetativa
    exit;
  end;

  for iposte := 0 to globs.NPostes - 1 do
  begin
    PGen[iposte] := s.xval(ivar + iposte);
    PDem[iposte] := s.xval(ivar + globs.npostes + iposte);
    P[iposte] := PGen[iposte] - PDem[iposte];
    Lambda_P[iPoste] := s.xmult(ivar + iposte) / globs.DurPos[iPoste];
    cv_Spot[iPoste] := Nodo.cmarg[iposte] - Lambda_P[iPoste];
  end;

  Ingreso_PorDisponibilidad_ :=
    pa.CapacidadMaxima_MWh * NMaquinasDisponibles * pa.pagoPorDisponibilidad_USDxMWhh *
    globs.HorasDelPaso;
  Xs_Carga := s.xval(ivar + 2 * globs.NPostes);

  CostoDegradacionVariable := PorcionDegradada(X_Carga, Xs_Carga) * cvRecambio_USD;
  CostoDirectoDelPaso := CostoDirectoDelPaso + CostoDegradacionVariable;

end;


procedure TBancoDeBaterias01.EvolucionarEstado;
begin
  X_Carga := Xs_Carga;
end;

procedure TBancoDeBaterias01.CambioFichaPD;
var
  q: NReal;
  sq: NReal;
  AnualidadCosteo_USD_MWh_anio: NReal;
begin
  flg_UsarCostoDegradacionRecambio := True;
  if (pa.EnsayoCiclado_nc2 <= 0) or (pa.EnsayoCiclado_nc1 <= 0) or
    (pa.EnsayoCiclado_pd2 < AsumaCero) or (pa.EnsayoCiclado_pd1 < AsumaCero) or
    (pa.CostoRecambio_USD_kWh_instalado < AsumaCero) then
  begin
    flg_UsarCostoDegradacionRecambio := False;
    cvRecambio_USD := 0;
    cvDegDem := 0;
    cvDegGen := 0;
    exit;
  end;

  beta := -(ln(pa.EnsayoCiclado_nc2) - ln(pa.EnsayoCiclado_nc1)) /
    (ln(pa.EnsayoCiclado_pd2) - ln(pa.EnsayoCiclado_pd1));

  alfa := ln(pa.EnsayoCiclado_nc2) + beta * ln(pa.EnsayoCiclado_pd2);

  expmalfadiv2 := exp(-alfa) / 2.0;
  expmalfadiv2porbeta := expmalfadiv2 * beta;

  q := 1.0 / (1.0 + pa.TasaDescuento_costeo);

  //  serie geométrica usada para el costeo
  sq := (1.0 - power(q, pa.AniosRecambio_costeo)) / (1.0 - q);


  // Anulidad en USD/(año y MWh_instalado)
  AnualidadCosteo_USD_MWh_anio := (pa.CostoRecambio_USD_kWh_instalado * 1.0e3) / sq;

  PorcionFija := 0.1; // Parte que suponemos se consume por el paso del tiempo
  // use o no la batería.

  cfRecambio_USD_MWh_instalado_PorPasoT :=
    (PorcionFija * AnualidadCosteo_USD_MWh_anio / 8760.0 * globs.HorasDelPaso);
  cvRecambio_USD_MWh_instalado :=
    (1.0 - PorcionFija) * AnualidadCosteo_USD_MWh_anio * pa.AniosRecambio_costeo;

end;


function TBancoDeBaterias01.getNombreVar(ivar: integer; var nombre: string): boolean;
begin
  if (ivar >= self.ivar) and (ivar < self.ivar + 2 * globs.NPostes) then
  begin
    Result := True;
    if (ivar < self.ivar + globs.NPostes) then
      nombre := self.Nombre + '_PGen[MW]' + IntToStr(ivar - self.ivar + 1)
    else
      nombre := self.Nombre + '_PDem[MW]' + IntToStr(ivar - self.ivar -
        globs.NPostes + 1);
  end
  else
    Result := False;
end;

function TBancoDeBaterias01.getNombreRes(ires: integer; var nombre: string): boolean;
begin
  if (ires = self.ires) then
  begin
    nombre := self.nombre + 'f(x,u,r)-Xs=0';
    Result := True;
  end
  else
    Result := False;
end;

procedure TBancoDeBaterias01.PubliVars;
begin
  inherited PubliVars;
  PublicarVariableVR('PGen', '[MW]', 6, 2, PGen, True, True);
  PublicarVariableVR('PDem', '[MW]', 6, 2, PDem, True, True);
  PublicarVariableNR('cv_MWh', '[USD/MWh]', 6, 1, cv_MWh, True);
  PublicarVariableNR('X_Carga', '[MWh]', 6, 1, X_Carga, True);
  PublicarVariableNI('NMaquinasDisponibles', '-', NMaquinasDisponibles, False);

  PublicarVariableNR('cfRecambio_USD_MWh_instalado_PorPasoT', '[USD]',
    6, 1, cfRecambio_USD_MWh_instalado_PorPasoT, True);
  PublicarVariableNR('cvDegDem', '[USD/MWh]', 6, 1, cvDegDem, True);
  PublicarVariableNR('cvDegGen', '[USD/MWh]', 6, 1, cvDegGen, True);
end;

procedure TBancoDeBaterias01.optx_nvxs(var ixr, ixd, iauxr, iauxd: integer);
begin
  if not flg_ValorizadoManual then
  begin
    self.ixr := ixr;
    ixr := ixr + 1;
  end;
end;

procedure TBancoDeBaterias01.PosicionarseEnEstrellita;
begin
  if not flg_ValorizadoManual then
  begin
    X_Carga := globs.CF.xr[ixr] * CapacidadInstalada_MWh;
    Xs_Carga := X_Carga;
  end;
end;

procedure TBancoDeBaterias01.ActualizarEstadoGlobal(flg_Xs: boolean);
begin
  if not flg_ValorizadoManual then
    if CapacidadInstalada_MWh > AsumaCero then
      globs.CF.xr[ixr] := X_Carga / CapacidadInstalada_MWh
    else
      globs.CF.xr[ixr] := 0;
end;

procedure TBancoDeBaterias01.optx_RegistrarVariablesDeEstado(
  adminEstados: TAdminEstados);
begin
  if not flg_ValorizadoManual then
    adminEstados.Registrar_Continua(
      ixr,
      0.0,
      1.0,
      ndisc,
      nombre + '_Carga', // nombre de la variable
      'p.u.' // unidades de la capacidad instalada en cada momento
      );
end;

destructor TBancoDeBaterias01.Destroy;
begin
  if lpd <> nil then
    lpd.Free;
  setlength(PGen, 0);
  setlength(PDem, 0);

  setlength(PMax_Gen_PorPoste, 0);
  setlength(PMax_Dem_PorPoste, 0);

  setlength(X_Carga_min, 0);
  setlength(X_Carga_max, 0);

  inherited Destroy;
end;



{*********************************
*Métodos de TFichaBancoBaterias *
*********************************}

constructor TFichaBancoDeBaterias01.Create(capa: integer; fecha: TFecha;
  periodicidad: TPeriodicidad; PMax_Gen, PMax_Dem: NReal; ren_Gen, ren_Dem: NReal;
  CapacidadMaxima_MWh: NReal;
  fd, TMR, cv_MWh_ValorizadoManual, pagoPorDisponibilidad_USDxMWhh,
  EnsayoCiclado_pd1: NReal; EnsayoCiclado_nc1: integer; EnsayoCiclado_pd2: NReal;
  EnsayoCiclado_nc2: integer; CostoRecambio_USD_kWh_instalado: NReal;
  AniosRecambio_costeo: integer; TasaDescuento_costeo: NReal);
begin
  inherited Create(capa, fecha, periodicidad);

  Self.PMax_Gen := PMax_Gen;
  self.PMax_Dem := PMax_Dem;
  self.ren_Gen := ren_Gen;
  self.ren_Dem := ren_Dem;
  self.CapacidadMaxima_MWh := CapacidadMaxima_MWh;
  self.fd := fd;
  self.TMR := TMR;
  self.cv_MWh_ValorizadoManual := cv_MWh_ValorizadoManual;
  self.pagoPorDisponibilidad_USDxMWhh := pagoPorDisponibilidad_USDxMWhh;
  self.AniosEnvejecimientoNatural_ELIMINADO_ := 0;
  self.EnsayoCiclado_pd1 := EnsayoCiclado_pd1;
  self.EnsayoCiclado_nc1 := EnsayoCiclado_nc1;
  self.EnsayoCiclado_pd2 := EnsayoCiclado_pd2;
  self.EnsayoCiclado_nc2 := EnsayoCiclado_nc2;
  self.CostoRecambio_USD_kWh_instalado := CostoRecambio_USD_kWh_instalado;
  self.AniosRecambio_costeo := AniosRecambio_costeo;
  self.TasaDescuento_costeo := TasaDescuento_costeo;

end;


function TFichaBancoDeBaterias01.Rec: TCosa_RecLnk;
begin
  Result := inherited Rec;
  Result.addCampoDef('PMax_Gen', PMax_Gen);
  Result.addCampoDef('PMax_Dem', PMax_Dem);
  Result.addCampoDef('ren_Gen', ren_Gen);
  Result.addCampoDef('ren_Dem', ren_Dem);
  Result.addCampoDef('CapacidadMaxima_MWh', CapacidadMaxima_MWh);
  Result.addCampoDef('fd', fd);
  Result.addCampoDef('TMR', TMR);
  Result.addCampoDef('cv_MWh_ValorizadoManual', cv_MWh_ValorizadoManual, 121);
  Result.addCampoDef('pagoPorDisponibilidad_USDxMWhh',
    pagoPorDisponibilidad_USDxMWhh, 149);

  Result.addCampoDef('EnsayoCiclado_pd1', EnsayoCiclado_pd1, 191);
  Result.addCampoDef('EnsayoCiclado_nc1', EnsayoCiclado_nc1, 191);
  Result.addCampoDef('EnsayoCiclado_pd2', EnsayoCiclado_pd2, 191);
  Result.addCampoDef('EnsayoCiclado_nc2', EnsayoCiclado_nc2, 191);
  Result.addCampoDef('AniosEnvejecimientoNatural',
    AniosEnvejecimientoNatural_ELIMINADO_, 191, 199);
  //Result.addCampoDef('CapacidadFinal_pu', CapacidadFinal_pu, 191);
  Result.addCampoDef('CostoRecambio_USD_kWh_instalado',
    CostoRecambio_USD_kWh_instalado, 191);

  Result.addCampoDef('AniosRecambio_costeo', AniosRecambio_costeo, 191);
  Result.addCampoDef('TasaDescuento_costeo', TasaDescuento_costeo, 191);

end;

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

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

procedure TBancoDeBaterias01_cambioFichaPD(Actor: TCosa);
begin
  (Actor as TBancoDeBaterias01).cambioFichaPD;
end;



destructor TFichaBancoDeBaterias01.Destroy;
begin
  inherited Destroy;
end;


procedure AlInicio;
begin
  registrarClaseDeCosa(TBancoDeBaterias01.ClassName, TBancoDeBaterias01);
  registrarClaseDeCosa(TFichaBancoDeBaterias01.ClassName, TFichaBancoDeBaterias01);
end;

procedure AlFinal;
begin
end;

end.
