{xDEFINE HOLGURA_FALLA}
{$DEFINE CALC_DEMANDA_NETA}
{$DEFINE BUGFIX_viie27}
unit uDemandas;
(*** DEFINE EL PADRE DE TODAS LAS DEMANDAS *****)
interface

uses
  unodos, uActorNodal, uFechas, xMatDefs, SysUtils,
  usimplex, Classes,
  ucosa, uCosaConNombre,
  uConstantesSimSEE, uFuentesAleatorias,
  uglobs, Math,
  uauxiliares,
  uFichasLPD;

type
  { TListDemanda }

  TListDemanda = class(TListaDeCosasConNombre)
  end;


  { TDemanda }

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

    falla_profundidad: TDAOfNReal; //profundidad de los escalones en p.u.
    falla_costo_0: TDAofNReal;

    fuente: TFuenteAleatoria;
    nombreBorne: string;
    icf_Fuente: TFuenteAleatoria;
    icf_NombreBorne: string;
    {Indica si debe sumar el valor de la bornera fuente a la potencia o debe utilizarlo como coeficiente P*(1+borne)}
    SumarEnergiaHr: boolean;
    flg_SumarParaPostizado: boolean;
    fReserva: NReal;
    (**************************************************************************)

    PPA: TDAOfNReal; // potencias por poste actuales (pos-sorteos).

    falla_costo_indexado: TDAOfNReal; // costo de falla en USD/MWh

    fallas: array of TDAOfNReal; // potencias de cada escalón fallas en cada poste
    // hay un vector por cada escalón y en cada vector
    // están las potencias despachadas de ese escalón
    // en cada poste.

    costos: array of TDAOfNReal; //Costos resultantes del paso para cada
    //escalon para cada poste.
    //costos[iEscalon][iPoste] = fallas[iEscalon][iPoste] * durPos[iPoste] * falla_costo_indexado[iEscalon]

    numeroBorne: integer;
    icf_kBorne: integer;
    icf_Valor: NReal;

    iaRand: NReal; // factor aleatorio de la demana  (1 + fuente)

    // SinAfectar / Afectada por la fuente de ruido que suma o multiplica
    PHoraria_SinAfectar, PHoraria_Afectada: TDAOfNReal;

    {$IFNDEF CALC_DEMANDA_NETA}
    // auxiliar, cada demanda carga el detalle del paso y luego se postiza
    idxHorasPostizadas: TDAOfNInt;
    {$ENDIF}

    NMaquinasDisponibles: integer;


    constructor Create(capa: integer; nombre: string; nacimiento, muerte: TFecha;
      lpdUnidades: TFichasLPD; nodo: TNodo;
      falla_profundidad, falla_costo: TDAOfNReal; fuente: TFuenteAleatoria;
      nombreBorne: string; icf_Fuente: TFuenteAleatoria; icf_NombreBorne: string;
      SumarEnergiaHr, flg_SumarParaPostizado: boolean; PrioridadParaSpot: integer;
      xFuenteIdxP: TFuenteAleatoria; xBorneIdxP: string );

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

    function NEscalonesDeFalla: integer;
    procedure PrepararMemoria(Catalogo: TCatalogoReferencias; globs: TGlobs); override;

    {$IFNDEF CALC_DEMANDA_NETA}
    procedure prepararPaso_as; override;
    {$ENDIF}

    {$IFDEF CALC_DEMANDA_NETA}
    procedure prepararPaso_ps_pre; override;
    {$ENDIF}
    procedure PrepararPaso_ps; override;

    procedure opt_cargue(s: TSimplex); override;
    procedure SorteosDelPaso(sortear: boolean); override;

    function demandaPromedio(fechaIni, fechaFin: TFecha): NReal; virtual; abstract;
    function demandaMaxima(fechaIni, fechaFin: TFecha): NReal; virtual; abstract;

    procedure opt_nvers(var ivar, ivae, ires: integer); 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 CalcularPagoServicioDeConfiabilidaddelSist; override;


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

procedure AlInicio;
procedure AlFinal;


(* Escalones de falla por defecto
( Uruguay-invierno 2006 )
escf [pu]  0.05  0.075  0.075  0.8
cvf [USD/MWh]  250  400  1200  2000
*)
(* Retorna un array con las profundidades de la falla por defecto *)
function ProfundidadEscalonesDeFallaPorDefecto: TDAOfNReal;
(* Retorna un array con los costos de falla por defecto *)
function CostoEscalonesDeFallaPorDefecto: TDAOfNReal;

implementation

{ TListDemanda }

constructor TDemanda.Create(capa: integer; nombre: string; nacimiento,
  muerte: TFecha; lpdUnidades: TFichasLPD; nodo: TNodo; falla_profundidad,
  falla_costo: TDAOfNReal; fuente: TFuenteAleatoria; nombreBorne: string;
  icf_Fuente: TFuenteAleatoria; icf_NombreBorne: string; SumarEnergiaHr,
  flg_SumarParaPostizado: boolean; PrioridadParaSpot: integer;
  xFuenteIdxP: TFuenteAleatoria; xBorneIdxP: string);
begin
  inherited Create(capa, nombre, nacimiento, muerte, lpdUnidades, nodo, xFuenteIdxP, xBorneIdxP );
  self.falla_profundidad := copy(falla_profundidad, 0, length(falla_profundidad));
  self.falla_costo_0 := copy(falla_costo);
  self.fuente := fuente;
  self.nombreBorne := nombreBorne;
  self.icf_Fuente := icf_Fuente;
  self.icf_NombreBorne := icf_NombreBorne;
  self.SumarEnergiaHr := SumarEnergiaHr;
  self.flg_SumarParaPostizado := flg_SumarParaPostizado;
  self.Prioridad_DemSpot:= PrioridadParaSpot;
end;

function TDemanda.Rec: TCosa_RecLnk;
begin
  Result:=inherited Rec;

  Result.addCampoDef('falla_profundidad', falla_profundidad);
  Result.addCampoDef('falla_costo', falla_costo_0);
  Result.addCampoDef_ref('fuente', TCosa(fuente), Self);
  Result.addCampoDef('nombreBorne', nombreBorne);
  Result.addCampoDef_ref('indiceCostoDeFalla', TCosa(self.icf_Fuente), Self, 8 );
  Result.addCampoDef('borneIndiceCostoDeFalla', self.icf_NombreBorne, 8 );
  Result.addCampoDef('SumarEnergiaHr', SumarEnergiaHr, 74 );
  Result.addCampoDef('flg_SumarParaPostizado', flg_SumarParaPostizado, 110, 0, 'T' );
  Result.addCampoDef('fReserva', fReserva, 129 );

end;

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

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

(* Escalones de falla por defecto
( Uruguay-invierno 2006 )
escf [pu]  0.05  0.075  0.075  0.8
cvf [USD/MWh]  250  400  1200  2000
*)
function ProfundidadEscalonesDeFallaPorDefecto: TDAOfNReal;
var
  a: TDAOfNReal;
begin
  setlength(a, 4);
  a[0] := 0.05;
  a[1] := 0.075;
  a[2] := 0.075;
  a[3] := 0.8;
  Result := a;
end;

function CostoEscalonesDeFallaPorDefecto: TDAOfNReal;
var
  a: TDAOfNReal;
begin
  setlength(a, 4);
  a[0] := 250.0;
  a[1] := 400.0;
  a[2] := 1200.0;
  a[3] := 2000.0;
  Result := a;
end;

function TDemanda.NEscalonesDeFalla: integer;
begin
  Result := length(falla_profundidad);
end;

procedure TDemanda.PrepararMemoria(Catalogo: TCatalogoReferencias; globs: TGlobs);
var
  iescalon: integer;
begin
  inherited PrepararMemoria(Catalogo, globs);

  setlength(PPA, globs.NPostes);
  // creamos el espacio para el resultado del despacho de las fallas y sus costos
  setlength(fallas, length(falla_profundidad));
  SetLength(costos, length(falla_profundidad));
  for iescalon := 0 to high(fallas) do
  begin
    setlength(fallas[iescalon], globs.NPostes);
    SetLength(costos[iescalon], globs.NPostes);
  end;

  setlength(falla_costo_indexado, length(fallas));

  if fuente <> nil then
    numeroBorne := fuente.idBorne(nombreBorne);
  if self.icf_Fuente <> nil then
    icf_kBorne := self.icf_Fuente.IdBorne(self.icf_NombreBorne);

  setlength( PHoraria_SinAfectar, ceil(globs.HorasDelPaso));
  setlength( PHoraria_Afectada, length( PHoraria_SinAfectar ) );


 {$IFNDEF CALC_DEMANDA_NETA}
  setlength(idxHorasPostizadas, ceil(globs.HorasDelPaso));

  // por ahora hago esto para que la primer demanda que aparezca
  // sea la principal.
  if globs.GetDemandaPrincipal = nil then
    globs.SetDemandaPrincipal(Self);
  {$ENDIF}

end;


procedure TDemanda.opt_cargue(s: TSimplex);
var
  iposte, iescalon: integer;
  ibaseres: integer;
begin
  if NMaquinasDisponibles = 0 then
    exit;

  ibaseres := nodo.ires;
  // Carga el término constante de las restricciones de demanda
  for iposte := 0 to high(PPa) do
    s.acum_e(ibaseres + iposte, s.nc, -PPa[iposte]);


  // Ahora corresponde cargar las máquinas de falla
  for iescalon := 0 to high(falla_profundidad) do
    for iposte := 0 to globs.NPostes - 1 do
    begin
      // cargamos la potencia en la restricción del nodo
      s.pon_e(ibaseres + iposte, ivar + iescalon * globs.NPostes + iposte, 1);
      // cargamos el coeficiente del costo en la función objetivo
      if PPa[iPoste] >= 0 then
        s.pon_e(s.nf, ivar + iescalon * globs.NPostes + iposte,
          -falla_costo_indexado[iescalon] * globs.durpos[iposte])
      else // Si la potencia de la Demanda es Negativa le cambio el signo al costo
        // para que refleje que tengo que PAGAR para que se lleven la energía por la Falla.
        s.pon_e(s.nf, ivar + iescalon * globs.NPostes + iposte,
          falla_costo_indexado[iescalon] * globs.durpos[iposte]);
    end;
end;

procedure TDemanda.SorteosDelPaso(sortear: boolean);
begin
  if globs.ObligarDisponibilidad_1_ then
    NMaquinasDisponibles := paUnidades.nUnidades_Operativas[0]
  else if sortear then
  begin
    ActualizarProbabilidadesReparacionYRotura_(1, 9); //pa.disp, pa.tRepHoras);
    NMaquinasDisponibles := Sorteos_RepRotUnidades;
  end
  else
    NMaquinasDisponibles := paUnidades.nUnidades_Operativas[0];

  if NMaquinasDisponibles = 0 then
    vclear(PHoraria_SinAfectar)
  else if NMaquinasDisponibles > 1 then
    vmultr(PHoraria_SinAfectar, NMaquinasDisponibles);
end;


{$IFNDEF CALC_DEMANDA_NETA}
procedure TDemanda.PrepararPaso_as;
var
  iposte: integer;
  hora: integer;
  nHorasDelPoste: integer;
  EnergiaDelPoste: NReal;
  jhora: integer;
  k: integer;
begin
  if not globs.SalaMinutal then
  begin
    if self = globs.GetdemandaPrincipal then
    begin
      // inicializamos el índice
      for k := 0 to high(idxHorasPostizadas) do
        idxHorasPostizadas[k] := k;
      if globs.PostesMonotonos then
        QuickSort_Decreciente( PHoraria_SinAfectar, idxHorasPostizadas);
      hora := 0;

      for iposte := 0 to globs.NPostes - 1 do
      begin
        nHorasDelPoste := round(globs.Durpos[iposte]);
        EnergiaDelPoste := 0;

        for jhora := 0 to nHorasDelPoste - 1 do
        begin
          EnergiaDelPoste := EnergiaDelPoste + PHoraria_SinAfectar[hora];

          globs.kPosteHorasDelPaso[idxHorasPostizadas[hora]] := iposte;
          Inc(hora);
        end;
        PPA_PreSorteos[iposte] := EnergiaDelPoste / nHorasDelPoste;
      end;
    end
    else
    begin
      vclear(PPA_PreSorteos);
      for hora := 0 to high(PHoraria_) do
      begin
        iposte := globs.kPosteHorasDelPaso[hora];
        PPA_PreSorteos[iposte] := PPA_PreSorteos[iposte] + PHoraria_[hora];
      end;
      for iposte := 0 to high(PPA) do
      begin
        nHorasDelPoste := round(globs.Durpos[iposte]);
        PPA_PreSorteos[iposte] := PPA_PreSorteos[iposte] / nHorasDelPoste;
      end;
    end;
  end
  else
  begin
    PPA_PreSorteos[0] := PHoraria_[0];
  end;
end;

{$ENDIF}



{$IFDEF CALC_DEMANDA_NETA}
procedure TDemanda.prepararPaso_ps_pre;
var
  i: integer;
  av: NReal;
begin
  if fuente <> nil then
  begin
    if SumarEnergiaHr then
    begin //si es usado como incremento de la demanda
      av := fuente.bornera[numeroBorne];
      for i := 0 to high( PHoraria_Afectada ) do
        PHoraria_Afectada[i] := PHoraria_SinAfectar[i] + av;
    end
    else
    begin //si es usado como factor de alteración de la demanda
      av := 1 + fuente.bornera[numeroBorne];
(*       if av < 0 then
         av := 0.0; *)

      for i := 0 to high( PHoraria_Afectada ) do
        PHoraria_Afectada[i] := PHoraria_SinAfectar[i] * av;
    end;
  end
  else
   vcopy( PHoraria_Afectada, PHoraria_SinAfectar );

  if flg_SumarParaPostizado then
    globs.sumarPHoraria( PHoraria_Afectada );
end;

{$ENDIF}

procedure TDemanda.PrepararPaso_ps;
var
  iposte: integer;
  jHora: integer;
  iEscalon: integer;
begin

  {$IFDEF CALC_DEMANDA_NETA}
  if globs.SalaMinutal then
  begin
    PPA[0] := PHoraria_Afectada[0];
  end
  else
  begin
    vclear(PPA);
    for jHora := 0 to high(globs.idxHorasPostizadas) do
    begin
      iposte := globs.kPosteHorasDelPaso[jHora];
      PPA[iposte] := PPA[iposte] + PHoraria_Afectada[jHora];
    end;
    for iposte := 0 to globs.NPostes - 1 do
      PPA[iposte] := PPA[iposte] / globs.Durpos[iposte];
  end;
  {$ENDIF}

  if self.icf_Fuente <> nil then
  begin
    icf_Valor := self.icf_Fuente.Bornera[icf_kBorne];
    for iEscalon := 0 to high(self.falla_costo_indexado) do
      self.falla_costo_indexado[iEscalon] := self.falla_costo_0[iEscalon] * icf_Valor;
  end
  else
  begin
    icf_Valor := 1;
    for iEscalon := 0 to high(self.falla_costo_indexado) do
      self.falla_costo_indexado[iEscalon] := self.falla_costo_0[iEscalon];
  end;

end;



destructor TDemanda.destroy;
var
  i: integer;
begin
  SetLength(falla_profundidad, 0);
  SetLength(falla_costo_0, 0);
  SetLength(falla_costo_indexado, 0);
  for i := 0 to high(costos) do
    SetLength(costos[i], 0);
  SetLength(costos, 0);
  SetLength(PPa, 0);
  for i := 0 to high(fallas) do
    SetLength(fallas[i], 0);
  SetLength(fallas, 0);
  SetLength( PHoraria_SinAfectar, 0);
  SetLength( PHoraria_Afectada, 0);
  {$IFNDEF CALC_DEMANDA_NETA}
  SetLength(idxHorasPostizadas, 0);
  {$ENDIF}
  inherited destroy;
end;

procedure TDemanda.opt_nvers(var ivar, ivae, ires: integer);
begin
  if NMaquinasDisponibles = 0 then
    exit;
  Self.ivar := ivar;
  // tengo para cada escalón de falla una variable por poste
  ivar := ivar + NEscalonesDeFalla * globs.NPostes;
end;

procedure TDemanda.opt_fijarRestriccionesDeCaja(s: TSimplex);
var
  iescalon, iposte: integer;
  Fpmax: NReal;
begin
  if NMaquinasDisponibles = 0 then
    exit;

  if length( falla_profundidad ) = 0 then exit;

  for iescalon := 0 to high(falla_profundidad) - 1 do
    // Le fijamos la potencia máxima de cada escalón en cada poste

    for iposte := 0 to globs.NPostes - 1 do
    begin
{$IFDEF BUGFIX_viie27} //rch@20200316
      Fpmax := PPa[iposte] * Self.falla_profundidad[iescalon];
      if Fpmax > AsumaCero then
        s.cota_sup_set(ivar + iescalon * globs.NPostes + iposte, Fpmax)
      else if Fpmax < -AsumaCero then
      begin
        s.cota_inf_set(ivar + iescalon * globs.NPostes + iposte, Fpmax);
        s.cota_inf_set(ivar + iescalon * globs.NPostes + iposte, 0);
      end
      else
        s.FijarVariable( ivar + iescalon * globs.NPostes + iposte, 0 );
{$ELSE}
      Fpmax := PPa[iposte] * Self.falla_profundidad[iescalon];
      if Fpmax > 0 then
        s.cota_sup_set(ivar + iescalon * globs.NPostes + iposte, Fpmax)
      else
        s.cota_inf_set(ivar + iescalon * globs.NPostes + iposte, Fpmax);
{$ENDIF}
    end;

  // al último escalón le ponemos un poquitito más para que siempre sea capaz de
  // satisfacer la demanda sin iportar los errores numéricos
  iescalon := high(falla_profundidad);
  // Le fijamos la potencia máxima de cada escalón en cada poste
  for iposte := 0 to globs.NPostes - 1 do
  begin
{$IFDEF HOLGURA_FALLA}
    Fpmax := PPa[iposte] * Self.falla_profundidad[iescalon] * 1.002;
{$ELSE}
    Fpmax := PPa[iposte] * Self.falla_profundidad[iescalon];
{$ENDIF}
    if Fpmax >= 0 then
      s.cota_sup_set(ivar + iescalon * globs.NPostes + iposte, Fpmax)
    else
    begin
      s.cota_inf_set(ivar + iescalon * globs.NPostes + iposte, Fpmax);
      s.cota_sup_set(ivar + iescalon * globs.NPostes + iposte, 0);
    end;
  end;

{$IFDEF GATILLOS_CAMBIOVAR}
  // Gatillamos los cambios de variables para dar con una solución fatible
  // desde el inicio
  for iescalon := 0 to high(falla_profundidad) do
    for iposte := 0 to globs.NPostes - 1 do
      s.GatillarCambioVarCotaSup(ivar + iescalon * globs.NPostes + iposte);
{$ENDIF}
end;

procedure TDemanda.opt_leerSolucion(s: TSimplex);
var
  cvf, fallaIJ: NReal;
  iescalon, iposte: integer;
  m: NReal;
begin
  // recuperamos los valores de Potencia de cada escalón de falla en cada poste.
  costoDirectoDelPaso := 0;
  vclear(P);
  if NMaquinasDisponibles = 0 then
  begin
    for iescalon := 0 to high(falla_profundidad) do
    begin
      vclear(fallas[iescalon]);
      vclear(costos[iescalon]);
    end;
    exit;
  end;

  for iescalon := 0 to high(falla_profundidad) do
  begin
    cvf := falla_costo_indexado[iescalon];
    for iposte := 0 to globs.NPostes - 1 do
    begin
      fallaIJ := s.xval(ivar + iescalon * globs.NPostes + iposte);
      P[iposte] := P[iposte] + fallaIJ;
      fallas[iescalon][iposte] := fallaIJ;
      m := fallaIJ * cvf * globs.DurPos[iposte];
      if PPA[iposte] < 0 then
         m := -m; // si la demanda es negativa el CostoDeFalla cambia de signo
      costos[iescalon][iposte] := m;
      costoDirectoDelPaso := costoDirectoDelPaso + m;
    end;
  end;
  for iposte := 0 to globs.NPostes - 1 do
    P[iposte] := P[iposte] - PPA[iposte];
end;


function TDemanda.getNombreVar(ivar: integer; var nombre: string): boolean;
var
  iEscalon: integer;
  iPoste: integer;
begin
  if (ivar >= self.ivar) and (ivar < self.ivar + NEscalonesDeFalla * globs.NPostes) then
  begin
    iEscalon := (ivar - self.ivar) div (globs.NPostes) + 1;
    iPoste := (ivar - self.ivar) mod (globs.NPostes) + 1;
    nombre := self.nombre + '_[MW]F' + IntToStr(iescalon) + 'p' + IntToStr(iposte);
    Result := True;
  end
  else
    Result := False;
end;

function TDemanda.getNombreRes(ires: integer; var nombre: string): boolean;
begin
  Result := False;
end;

procedure TDemanda.CalcularPagoServicioDeConfiabilidaddelSist;
var
   iPoste: integer;
   CmgMenosTecho: NReal;
begin
    ParticipacionSCS:= 0;
    ForzamientoSCS:=0;
    for iPoste:= 0 to high( PPA )  do
    begin
       CmgMenosTecho:= nodo.cmarg[iposte] - globs.TechoDelSpot;
       if  CmgMenosTecho > 0  then
           ParticipacionSCS:= ParticipacionSCS + PPA[iposte]* globs.durpos[iposte] * CmgMenosTecho // al ltriangulito.
    end
end;


procedure TDemanda.PubliVars;
var
  iescalon: integer;
begin
  inherited PubliVars;
  PublicarVariableVR('PD', '[MW]', 6, 1, PPA, True, True);

  setlength( fallas, length( falla_profundidad ) );
  for iescalon := 0 to high(fallas) do
  begin
    setlength( fallas[iescalon], globs.NPostes );
    PublicarVariableVR('PF' + IntToStr(iescalon + 1), '[MW]', 6, 1,
      fallas[iescalon], True, True);
  end;

  setlength( costos, length( falla_profundidad ));
  for iescalon := 0 to high(fallas) do
    PublicarVariableVR('Costo' + IntToStr(iescalon + 1), '[USD]', 8, 1,
      costos[iescalon], True, True);
end;

procedure TDemanda.dump_Variables(var f: TextFile; charIndentacion: char);
var
  i: integer;
begin
  inherited dump_Variables(f, charIndentacion);
  for i := 0 to high(PPA) do
    writeln(f, charIndentacion, 'PPA[' + IntToStr(i + 1) + ']= ',
      FloatToStrF(PPA[i], ffFixed, 10, 3));
  writeln(f);
end;


procedure AlInicio;
begin
  ucosa.registrarClaseDeCosa(TListDemanda.ClassName, TListDemanda);
end;

procedure AlFinal;
begin

end;

end.
