unit udemandaCombustible;

{$MODE Delphi}

(*** DEFINE EL PADRE DE TODAS LAS DEMANDAS *****)
interface

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

type

  { TDemandaCombustible }

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

    //profundidad de los escalones en p.u.
    falla_profundidad: TDAOfNReal;
    falla_costo_0: TDAofNReal;
    fuente: TFuenteAleatoria;
    nombreBorne: string;
    //Indica si debe sumar el valor de la bornera fuente a la potencia o debe utilizarlo como coeficiente P*(1+borne)
    icf_Fuente: TFuenteAleatoria;
    icf_NombreBorne: string;
    SumarCaudalHr: boolean;

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

    falla_costo_indexado: TDAOfNReal; // costo de falla en USD/m3
    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]

    Qpp_PreSorteos: TDAOfNREal; // caudal por poste pre-sorteos.
    QD: TDAOfNReal; // caudal efctivamente demandado por poste actuales.

    fallas: array of TDAOfNReal; // caudal 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.

    numeroBorne: integer;

    icf_kBorne: integer;
    icf_Valor: NReal;

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

    QHoraria: TDAOfNReal;


    constructor Create(capa: integer; nombre: string; nacimiento, muerte: TFecha;
      lpdUnidades: TFichasLPD; nodocomb: TNodoCombustible; falla_profundidad,
  falla_costo: TDAOfNReal; fuente: TFuenteAleatoria; nombreBorne: string;
  icf_Fuente: TFuenteAleatoria; icf_NombreBorne: string;
  SumarCaudalHr: boolean; 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}

    procedure PrepararPaso_ps; override;

    procedure opt_cargue(s: TSimplex); override;

    function demandaCombustiblePromedio(fechaIni, fechaFin: TFecha): NReal;
      virtual; abstract;
    function demandaCombustibleMaxima(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 PubliVars; override;
    procedure dump_Variables(var f: TextFile; charIndentacion: char); override;
    procedure Free; override;

  end;


(* Escalones de falla por defecto, pedir datos Ruben
( Uruguay 2014 )
escf [pu]
cvf [USD/m3]
*)
(* 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


constructor TDemandaCombustible.Create(capa: integer; nombre: string;
  nacimiento, muerte: TFecha; lpdUnidades: TFichasLPD; nodocomb: TNodoCombustible;
  falla_profundidad, falla_costo: TDAOfNReal; fuente: TFuenteAleatoria;
  nombreBorne: string; icf_Fuente: TFuenteAleatoria; icf_NombreBorne: string;
  SumarCaudalHr: boolean;
  xFuenteIdxP: TFuenteAleatoria; xBorneIdxP: string );
begin
  inherited Create(capa, nombre, nacimiento, muerte, lpdUnidades, nodocomb,
  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.SumarCaudalHr := SumarCaudalHr;
end;

function TDemandaCombustible.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(icf_Fuente), Self);
  Result.addCampoDef('borneIndiceCostoDeFalla', icf_NombreBorne);
  Result.addCampoDef('SumarCaudalHr', SumarCaudalHr);
end;

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

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






//  Definir cantidad de escalones de falla por defecto
//  Definir profundidad y costo de los escalones de falla por defecto

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 TDemandaCombustible.NEscalonesDeFalla: integer;
begin
  Result := length(falla_profundidad);
end;

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

  setlength(QD, globs.NPostes);
  setlength(Qpp_PreSorteos, 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 icf_Fuente <> nil then
    icf_kBorne := icf_Fuente.IdBorne(icf_NombreBorne);

  setlength(QHoraria, ceil(globs.HorasDelPaso));
end;


{$IFNDEF CALC_DEMANDA_NETA}
procedure TDemandaCombustible.PrepararPaso_as;
var
  iposte: integer;
  hora: integer;
  EnergiaDelPoste: NReal;
  jhora: integer;
  k: integer;
begin
  if not globs.SalaMinutal then
  begin
    vclear(Qpp_PreSorteos);
    for hora := 0 to high(QHoraria) do
    begin
      iposte := globs.kPosteHorasDelPaso[hora];
      Qpp_PreSorteos[iposte] := Qpp_PreSorteos[iposte] + QHoraria[hora];
    end;
    for iposte := 0 to globs.NPostes - 1 do
      Qpp_PreSorteos[iposte] := Qpp_PreSorteos[iposte] / globs.Durpos[iposte];
  end
  else
  begin
    Qpp_PreSorteos[0] := QHoraria[0];
  end;
end;

{$ENDIF}

procedure TDemandaCombustible.PrepararPaso_ps;
var
  iposte: integer;
  jHora: integer;
begin

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

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

  if fuente <> nil then
  begin
    if SumarCaudalHr then
    begin //si es usado como incremento de la demanda
      iaRand := 1.0;
      for iposte := 0 to high(QD) do
        QD[iposte] := Qpp_PreSorteos[iposte] + fuente.bornera[numeroBorne];
    end
    else
    begin //si es usado como factor de alteración de la demanda
      iaRand := 1 + fuente.bornera[numeroBorne];
      if iaRand < 0 then
        iaRand := 0.0;
      for iposte := 0 to high(QD) do
        QD[iposte] := Qpp_PreSorteos[iposte] * iaRand;
    end;
  end
  else
  begin
    iaRand := 1.0;
    for iposte := 0 to high(QD) do
      QD[iposte] := Qpp_PreSorteos[iposte];
  end;
end;

procedure TDemandaCombustible.opt_cargue(s: TSimplex);
var
  iposte, iescalon: integer;
  ibaseres: integer;
begin

  ibaseres := nodocomb.ires;
  // Carga el término constante de las restricciones de demanda
  for iposte := 0 to high(QD) do
    s.acum_e(ibaseres + iposte, s.nc, -QD[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 el caudal 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
      s.pon_e(s.nf, ivar + iescalon * globs.NPostes + iposte,
        -globs.durpos[iposte] * NodoComb.combustible.MWh_por_Q1h_ *
        falla_costo_indexado[iescalon]);
    end;
end;



procedure TDemandaCombustible.Free;
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(Qpp_PreSorteos, 0);
  SetLength(QD, 0);
  for i := 0 to high(fallas) do
    SetLength(fallas[i], 0);
  SetLength(fallas, 0);
  SetLength(QHoraria, 0);
  inherited Free;
end;

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

procedure TDemandaCombustible.opt_fijarRestriccionesDeCaja(s: TSimplex);
var
  iescalon, iposte: integer;
  Fpmax: NReal;
begin
  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
      Fpmax := QD[iposte] * Self.falla_profundidad[iescalon];
      s.cota_sup_set(ivar + iescalon * globs.NPostes + iposte, Fpmax);
    end;

  // al último escalón le ponemos un poquitito más para que siempre sea capaz de
  // satisfacer la demanda sin importar 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
    Fpmax := QD[iposte] * Self.falla_profundidad[iescalon];
    s.cota_sup_set(ivar + iescalon * globs.NPostes + iposte, Fpmax);
  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 TDemandaCombustible.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(Q);
  for iescalon := 0 to high(falla_profundidad) do
  begin
    cvf := NodoComb.combustible.MWh_por_Q1h_ * falla_costo_indexado[iescalon];
    for iposte := 0 to globs.NPostes - 1 do
    begin
      fallaIJ := s.xval(ivar + iescalon * globs.NPostes + iposte);
      Q[iposte] := Q[iposte] + fallaIJ;
      fallas[iescalon][iposte] := fallaIJ;
      m := fallaIJ * globs.DurPos[iposte] * cvf;
      costos[iescalon][iposte] := m;
      costoDirectoDelPaso := costoDirectoDelPaso + m;
    end;
  end;
  for iposte := 0 to globs.NPostes - 1 do
    Q[iposte] := Q[iposte] - QD[iposte];
end;


function TDemandaCombustible.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 + 'Q_[m3/s]_F' + IntToStr(iescalon) + '_p' + IntToStr(iposte);
    Result := True;
  end
  else
    Result := False;
end;

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

procedure TDemandaCombustible.PubliVars;
var
  iescalon: integer;
begin
  inherited PubliVars;
  PublicarVariableVR('QD', '[m3/s]', 6, 1, QD, True, True);
  for iescalon := 0 to high(fallas) do
    PublicarVariableVR('QF' + IntToStr(iescalon + 1), '[m3/s]', 6, 1,
      fallas[iescalon], True, True);
  for iescalon := 0 to high(fallas) do
    PublicarVariableVR('Costo' + IntToStr(iescalon + 1), '[USD]', 8, 1,
      costos[iescalon], True, True);
end;

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




end.
