(*Se agrega la capacidad de tener requerimientos de reserva
al 8/5/15 por Micho mvarela@adme.com.uy*)
unit unodos;

{xDEFINE DEBUG_SANCION_SPOT}

interface

uses
  xmatdefs, usimplex, uglobs,
  SysUtils,
  Classes,
  ucosa,
  uCosaConNombre,
  uActores,
  //uINodos,
  ufichasLPD,
  uConstantesSimSEE,
  uFuentesAleatorias,
  ufechas;

resourcestring
  rsNodo = 'Nodo';

type

  { TListNodo }
  TListNodo = class(TListaDeCosasConNombre)

  end;



  { TNodo }

  TNodo = class(TActor)
  public

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

    ZonaFlucar: integer;
    Spot_max: double;
    Spot_min: double;

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


    // resultados
    cmarg: TDAOfNReal;
    spot: TDAOfNReal;

    cmarg_horario: TDAOfNReal;
    spot_horario: TDAOfNReal;

    cReserva: TDAOfNReal;
    //la idea es guardar los multiplicadores si son el costo de reserva.

    actgen_spot: array of TList;
    //guarda todos los generadores que participan en el Spot, ordenadas por su CV.
    actdem_spot: array of TList;
    // guarda todas las demandas que participan en el Spot, ordenadas por tipo.

    constructor Create(capa: integer; nombre: string; nacimiento, muerte: TFecha;
      lpdUnidades: TFichasLPD; ZonaFlucar: integer; Spot_max, Spot_min: double;
  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;

    function InfoAd_: string; override;
    class function DescClase: string; 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;
    function getNombreVar(ivar: integer; var nombre: string): boolean; override;
    function getNombreRes(ires: integer; var nombre: string): boolean; override;

    procedure sancionarSpot;
    procedure set_cmarg_horario;
    {$IFDEF DEBUG_SANCION_SPOT}
    procedure dump_listados_sancion_spot(lst_gen, lst_dem: TList);
    {$ENDIF}

    destructor destroy; override;

    procedure PubliVars; override;
  end;



procedure AlInicio;
procedure AlFinal;



implementation

uses
  uactornodal, uDemandas, uGeneradores, uarcos, uMercadoSpotConDetalleHorarioSemanal;



{ TListNodo }




//-------------------
// Métodos de TNodo
//===================
constructor TNodo.Create(capa: integer; nombre: string; nacimiento, muerte: TFecha;
  lpdUnidades: TFichasLPD; ZonaFlucar: integer; Spot_max, Spot_min: double;
  xFuenteIdxP: TFuenteAleatoria; xBorneIdxP: string );
begin
  inherited Create(capa, nombre, nacimiento, muerte, lpdUnidades,
  xFuenteIdxP, xBorneIdxP );
  Self.ZonaFlucar := ZonaFlucar;
  Self.Spot_max := Spot_max;
  Self.Spot_min := Spot_min;
end;

function TNodo.Rec: TCosa_RecLnk;
begin
  Result := inherited Rec;
  Result.addCampoDef('ZonaFlucar', ZonaFlucar, 66);
  Result.addCampoDef('SpotMax', Spot_max, 161, 0, '250');
  Result.addCampoDef('SpotMin', Spot_min, 161);
end;

procedure TNodo.BeforeRead(version, id_hilo: integer);
begin
  ZonaFlucar := 0;
  Spot_max := 250;
  Spot_min := 0;
  inherited BeforeRead(version, id_hilo);
end;

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




procedure TNodo.PrepararMemoria(Catalogo: TCatalogoReferencias; globs: TGlobs);
begin
  inherited prepararMemoria(Catalogo, globs);
  setlength(cmarg, globs.NPostes);
  setlength(spot, globs.NPostes);
  setlength(cmarg_horario, round( globs.HorasDelPaso ));
  setlength(spot_horario, round( globs.HorasDelPaso ));


  if globs.flg_ReservaRotante then
    setlength(cReserva, globs.NPostes);
  setlength(actgen_spot, globs.NPostes);
  setlength(actdem_spot, globs.NPostes);

end;

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

class function TNodo.DescClase: string;
begin
  Result := rsNodo;
end;

procedure TNodo.opt_nvers(var ivar, ivae, ires: integer);
begin

  Self.ires := ires;
  ires:= ires + globs.NPostes;
  if globs.flg_ReservaRotante then
  begin
    self.ires_ReservaRotante:= ires;
    ires := ires +  globs.NPostes;
  end;
end;

procedure TNodo.opt_cargue(s: TSimplex);
var
  ip: integer;
begin
  // nada, cada demanda carga el termino constante y cada generador los variables
  if globs.flg_ReservaRotante then
    for ip := 0 to 2 * globs.NPostes - 1 do
      s.FijarRestriccionIgualdad(ires + ip)//;
  else
    for ip := 0 to globs.NPostes - 1 do
      s.FijarRestriccionIgualdad(ires + ip);//;
end;

procedure TNodo.opt_fijarRestriccionesDeCaja(s: TSimplex);
begin
  // no tiene variables no tiene restricciones de caja
end;

procedure TNodo.opt_leerSolucion(s: TSimplex);
var
  iposte: integer;
begin
  // no tiene variables no tiene que leer las variables.
  // lo que podemos leer es el multiplicador de Lagrange para tener
  // el costo marginal del nodo.
  for iposte := 0 to globs.NPostes - 1 do
    cmarg[iposte] := -s.ymult(ires + iposte) * globs.invDurPos[iposte];
    {if usalasdejuego.reserva then
    begin
      for iposte := globs.NPostes to 2*globs.NPostes - 1 do
      cReserva[iposte] :=-s.ymult(ires + iposte) * globs.invDurPos[iposte]
    end;}
end;

function TNodo.getNombreVar(ivar: integer; var nombre: string): boolean;
begin
  Result := False;
end;




function Compare_gen(Item1, Item2: Pointer): integer;
var
  a1, a2: TActorNodal;
  iposte_Spot: integer;
begin
  a1 := Item1;
  a2 := Item2;
  iposte_Spot := a1.globs.iposte_spot;
  if a1.cv_Spot[iposte_Spot] > a2.cv_Spot[iposte_Spot] then
    Result := 1
  else if a1.cv_Spot[iposte_Spot] = a2.cv_Spot[iposte_Spot] then
    Result := 0
  else
    Result := -1;
end;

function Compare_dem(Item1, Item2: Pointer): integer;
var
  a1, a2: TActorNodal;
begin
  a1 := Item1;
  a2 := Item2;
  if a1.Prioridad_DemSpot > a2.Prioridad_DemSpot then
    Result := 1
  else if a1.Prioridad_DemSpot = a2.Prioridad_DemSpot then
    Result := 0
  else
    Result := -1;
end;



function TNodo.getNombreRes(ires: integer; var nombre: string): boolean;
begin
  if globs.flg_ReservaRotante then
  begin
    if (ires >= self.ires) and (ires < self.ires + 2 * globs.NPostes) then
    begin
      nombre := self.nombre + 'res-nodo' + IntToStr(ires - self.ires + 1);
      Result := True;
    end
    else
      Result := False;
  end
  else
  begin
    if (ires >= self.ires) and (ires < self.ires + globs.NPostes) then
    begin
      nombre := self.nombre + 'res-nodo' + IntToStr(ires - self.ires + 1);
      Result := True;
    end
    else
      Result := False;
  end;
end;


procedure TNodo.sancionarSpot;
var
  lst_gen, lst_dem: TList;
  iPoste_Spot, iGen, iDem, iescalon: integer;
  dem_tot_0, gen_tot_0, factor, gen_tot: double;
  cvf: double;
  aActor: TActorNodal;
  aDem: TDemanda;
  aGen: TActorUniNodal;
  aArc: TArco;

  hayfalla: boolean;
  a_cv: NReal;
begin

  for iposte_Spot := 0 to globs.NPostes - 1 do
  begin
    globs.iPoste_spot := iposte_Spot;
    //ordenamos generadores
    lst_gen := actgen_spot[iposte_Spot];
    lst_gen.Sort(@compare_gen);
    //ordenamos demandas
    lst_dem := actdem_spot[iposte_Spot];
    lst_dem.Sort(@compare_dem);
  end;

  {$IFDEF DEBUG_SANCION_SPOT}
  dump_listados_sancion_spot(lst_gen, lst_dem);
  {$ENDIF}

  // calcular spot por poste
  for iPoste_Spot := 0 to globs.NPostes - 1 do
  begin
    globs.iPoste_spot := iposte_Spot;
    dem_tot_0 := 0;
    lst_gen := actgen_spot[iPoste_Spot]; //?? esta bien esto???
    lst_gen.Sort(@compare_gen);
    lst_dem := actdem_spot[iPoste_Spot];
    lst_dem.Sort(@compare_dem);
    cvf := 0;
    hayfalla := False;

    // buscamos a ver si alguna demanda de prioridad 0 tiene falla
    // y cargamos en cvf el máximo costo de falla despachado
    for iDem := 0 to lst_dem.Count - 1 do
    begin
      aActor := lst_dem[iDem];
      if (aActor is TDemanda) and (aActor.Prioridad_DemSpot = 0) then
      begin
        iescalon := 0;
        aDem := aActor as TDemanda;
        while (iescalon <= High(aDem.falla_profundidad)) and
          (aDem.fallas[iescalon][iPoste_Spot] > AsumaCero) do
        begin
          if aDem.falla_costo_indexado[iescalon] > cvf then
            cvf := aDem.falla_costo_indexado[iescalon];
          Inc(iescalon);
          hayfalla := True;
        end;
      end;
    end;

    //sumo la demanda interna de Uy.
    for iDem := 0 to lst_dem.Count - 1 do
    begin
      aActor := lst_dem[iDem];
      if (aActor is TDemanda) and (aActor.Prioridad_DemSpot = 0) then
      begin
        aDem := aActor as TDemanda;
        if aDem.Prioridad_DemSpot = 0 then
          dem_tot_0 := dem_tot_0 + Tdemanda(lst_dem[iDem]).P[iPoste_Spot];
      end;
    end;
    dem_tot_0 := -dem_tot_0;


    gen_tot_0 := 0;
    gen_tot := 0;
    factor := 1.005;
    igen := 0;
    a_cv := 0;

    if lst_gen.Count > 0 then
    begin
      for igen := 0 to lst_gen.Count - 1 do
      begin
        aActor := lst_gen[igen];
        if (aActor is TGenerador) or (aActor is TMercadoSpotDetalleHorarioSemanal) then
        begin
          aGen := aActor as TActorUniNodal;
          gen_tot := gen_tot + aGen.P[iPoste_Spot];
          //sumo la generación total (para la demanda+exportación)
        end
        else if aActor is TArco then
        begin
          aArc := aActor as TArco;
          gen_tot := gen_tot + aArc.P_NodoB[iPoste_Spot];
          //sumo la generación total (para la demanda+exportación)
        end;
      end;


      if gen_tot > dem_tot_0 then
        factor :=
          1 //si hay exportación, cierro la generación de la demanda exacatamente iguales
      else
        factor := 1.005; //si no hay exportación consideror un error de redondeo

      igen := 0;
      aActor := lst_gen[igen];
      a_cv := aActor.cv_Spot[iPoste_spot];

      //sumo los generadores que aportaron a la demanda interna
      while ((gen_tot_0 * factor) < dem_tot_0) and
        (igen < lst_gen.Count) and (a_cv < 10000) do
      begin    //Ojo, aca tambien juega el despacho de miliwats de falla, jode a la primer concidicón.
        aActor := lst_gen[igen];
        //writeln( 'Margina: '+  TGenerador(lst_gen[igen]).nombre );
        if (aActor is TGenerador) or (aActor is TMercadoSpotDetalleHorarioSemanal) then
        begin
          aGen := aActor as TActorUniNodal;
          a_cv := aGen.cv_Spot[iPoste_spot];
          {$IFDEF _XC_DEBUG_}
          if a_cv > spot_max then
             WriteLn(aGen.nombre);
          {$ENDIF}
          gen_tot_0 := gen_tot_0 + aGen.P[iPoste_Spot];
        end
        else if aActor is TArco then
        begin
          aArc := aActor as TArco;
          a_cv := aArc.cv_Spot[iPoste_spot];
          if a_cv > spot_max then
             WriteLn(aArc.nombre);
          gen_tot_0 := gen_tot_0 + aArc.P_NodoB[iPoste_Spot];
        end;
        igen := igen + 1;
      end;
    end;


    if hayfalla then
      spot[iPoste_Spot] := cvf
    //si hay potencia de falla de demanda, va el cv del escalon de falla.
    else
    begin
      // POR AHORA, si el nodo no tiene demandas del tipo 0 el SPOT lo ponemos a cero.
      if igen > 0 then
      begin   // Atención el chanchuyo del or(abs... es para que el despacho de miliwats de falla
        // no imponga el spot_max. Me pasó que poniendo demandas con prioridad 1 (expbr) y CF = 30
        // despacha falla para cubrir la demanda y se ve que supera unos miliwats la PD y joroba
        // el cálculo del spot porque falta incorporar los generadores de falla al mecanismo
        if (igen < lst_gen.Count) or (dem_tot_0 <= gen_tot_0 * factor) then
        begin

          spot[iPoste_Spot] := a_cv; // TGenerador(lst_gen[igen-1]).cv_Spot[iPoste_Spot];
          //writeln( 'Margina: '+  TGenerador(lst_gen[igen-1]).nombre );
        end
        else
          spot[iPoste_Spot] := Spot_max;
        //No se cubrió la demanda con la generación existente.Falla del sistema
      end
      else
      begin
        if dem_tot_0 > 0 then
          spot[iPoste_Spot] := Spot_max // hay demanda y no hay generadores
        else
          spot[iPoste_Spot] := 0.0; // no hay demanda (no se si hay generadores)
      end;

    end;

    if spot[iPoste_spot] > spot_max then
      spot[iPoste_Spot] := spot_max;

  end;

end;

procedure TNodo.set_cmarg_horario;
var
  hora,iposte:integer;
begin
    for hora := 0 to Length(self.cmarg_horario) - 1 do
      begin
           iposte := globs.kPosteHorasDelPaso[hora];
           self.cmarg_horario[hora]:= self.cmarg[iposte];
           self.spot_horario[hora]:= self.spot[iposte];
      end
end;

{$IFDEF DEBUG_SANCION_SPOT}
procedure TNodo.dump_listados_sancion_spot(lst_gen, lst_dem: TList);
var
  f: textfile;
  iposte, iactor: integer;
  aActor: TActor;
  aGen: TActorUniNodal;
  aDem: TDemanda;
  aArc: TArco;

begin
  assignfile(f, 'dbg_sancion_spot_' + Self.nombre + '.xlt');
  rewrite(f);

  for iposte := 0 to globs.NPostes - 1 do
  begin
    writeln(f, 'POSTE: ', iPoste);
    writeln(f, 'Generadores');
    writeln(f, 'tipo', #9, 'Nombre', #9, 'P[MW]', #9, 'cv_Spot[USD/MWh]');
    for iActor := 0 to lst_gen.Count - 1 do
    begin
      aActor := lst_gen.items[iActor];
      if (aActor is TGenerador) or (aActor is TMercadoSpotDetalleHorarioSemanal) then
      begin
        aGen := aActor as TActorUniNodal;
        writeln(f, 'Gen', #9, aGen.Nombre, #9, aGen.P[iPoste], #9,
          aGen.cv_Spot[iposte]);
      end
      else if aActor is TArco then
      begin
        aArc := aActor as TArco;
        writeln(f, 'Arc', #9, aArc.Nombre, #9, aArc.P_NodoB[iPoste],
          #9, aArc.cv_Spot[iposte]);
      end;
    end;

    writeln(f, 'Demandas');
    writeln(f, 'tipo_PRI', #9, 'Nombre', #9, 'P[MW]', #9, 'cv_Spot[USD/MWh]');
    for iActor := 0 to lst_dem.Count - 1 do
    begin
      aActor := lst_dem.items[iActor];
      if aActor is TDemanda then
      begin
        aDem := aActor as TDemanda;
        writeln(f, 'Dem_' + IntToStr(aDem.Prioridad_DemSpot), #9,
          aDem.Nombre, #9, aDem.P[iPoste], #9, aDem.cv_Spot[iposte]);
      end
      else if aActor is TArco then
      begin
        aArc := aActor as TArco;
        writeln(f, 'Arc_' + IntToStr(aArc.Prioridad_DemSpot), #9,
          aArc.Nombre, #9, aArc.P_NodoA[iPoste], #9, aArc.cv_Spot[iposte]);
      end;
    end;
  end;
  closefile(f);

end;

{$ENDIF}


destructor TNodo.destroy;
var
  k: integer;
begin
  setlength(cmarg, 0);
  setlength(spot, 0);
  setlength(cmarg_horario, 0);
  setlength(spot_horario, 0);

  setlength(cReserva, 0);

  for k:= 0 to high( actgen_spot ) do
     actgen_spot[k].Free;
  setlength( actgen_spot, 0 );

  for k:= 0 to high( actdem_spot ) do
     actdem_spot[k].Free;
  setlength( actdem_spot, 0 );

  inherited destroy;
end;

procedure TNodo.PubliVars;
begin
  inherited PubliVars;
  PublicarVariableVR('cmg', '[USD/MWh]', 6, 1, cmarg, True, True);
  PublicarVariableVR('spot', '[USD/MWh]', 6, 1, spot, True, True);
  PublicarVariableVR('cmg_horario', '[USD/MWh]', 6, 1, cmarg_horario, True, True);
  PublicarVariableVR('spot_horario', '[USD/MWh]', 6, 1, spot_horario, True, True);

  {PublicarVariableVR('cReserva', '[USD/MWh]', 6, 1, cReserva, True, True);}
end;



procedure AlInicio;
begin
  registrarClaseDeCosa(TNodo.ClassName, TNodo);
  registrarClaseDeCosa(TListNodo.ClassName, TListNodo);
end;

procedure AlFinal;
begin
end;

end.
