{+doc
+NOMBRE:   uParqueEolico
+CREACION: 22/12/2006
+AUTORES:  rch
+REGISTRO:
+TIPO: Unidad Pascal.
+PROPOSITO:   Definición del actor TParqueEolico
+PROYECTO: SimSEE
+REVISION:
+AUTOR:
+DESCRIPCION:

-doc}

unit uParqueEolico;


interface

uses
  SysUtils,
  Classes,
  uFechas,
  uGeneradores,
  uFuentesAleatorias,
  uGlobs,
  uNodos,
  uCosa, uCosaConNombre,
  usimplex,
  uFichasLPD,
  uFuncionesReales,
  uConstantesSimSEE,
  xMatDefs;

resourcestring
  mesFuenteAleatConPaso1h_ =
    'TParqueEolico, necesita una fuente aleatoria con paso de sorteo = 1h.';
  mesElParque_ = 'El parque';
  mesConectadoAFuente_ = 'está conectado a la fuente';
  mesConPasoDeSorteo_ = 'con paso de sorteo';
  rsParqueEolico = 'Parque eólico';
  exCantSpeedupParque_ = 'La cantidad de factores de speedup en el parque eolico';
  exDebeSer12_ = 'debe ser 12';

type

  { TParqueEolico }
  TParqueEolico = class(TGeneradorPostizador)
  public
    (**************************************************************************)
    (*              A T R I B U T O S   P E R S I S T E N T E S               *)
    (**************************************************************************)

    fdisp: NReal;
    fSpeedUpMes: TDAofNReal;
    fPerdidasInterferencias: NReal;
    CurvaVP: TFVectR;
    fuenteDeViento: TFuenteAleatoria;
    nombreBorne: string;

    fReserva: NReal;

    PagoPorEnergiaDisponible_USD_MWh: NReal;
    tRepHoras: NReal;
    PagoPorEnergiaEntregada_USD_MWh: NReal; // [USD/MWh] para cálculo de CAD
    (**************************************************************************)

    //[USD/MWh] Pago por potencia disponible contemplando las unidades disponibles
    // resultado de los sorteos de disponibilidad y del mantenimiento programado y el viento

    kBorneVelocidad: integer;
    {$IFDEF CALC_DEMANDA_NETA}
    kBornePotencia: integer;
    {$ELSE}
    kBornePPA: TDAofNInt; // Potencia del Poste Actual
    {$ENDIF}

    fComplexivoMes: TDAofNReal;
    VVel: NReal;

    constructor Create(capa: integer; nombre: string;
      lpdUnidades: TFichasLPD; nodo: TNodo; flg_CalcularGradienteDeInversion: boolean;
      xFDV: TFuenteAleatoria; xNombreBorne: string; xfdisp: NReal;
      tRepHoras: NReal; xfSpeedUpMes: TDAofNReal; xfPerdidasInterferencias: NReal;
      xCurvaVelocidadPotencia_Pot: TFVectR; xPagoPorEnergiaEntregada_USD_MWh: NReal;
      xPagoPorEnergiaDisponible_USD_MWh: NReal; xflg_RestarParaPostizado: boolean;
      TonCO2xMWh: NReal; LowCostMustRun, CleanDevelopmentMechanism: boolean;
      xFuenteIdxP: TFuenteAleatoria; xBorneIdxP: string; fReservaRotante: NReal);

    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 PotenciaFirme: NReal; override;

    function InfoAd_: string; override;

    class function DescClase: string; 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 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;

    destructor destroy; override;
    procedure PubliVars; override;

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

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

  private
    px_fSpeedUP: NReal;
  end;




procedure AlInicio;
procedure AlFinal;

implementation


constructor TParqueEolico.Create(capa: integer; nombre: string;
  lpdUnidades: TFichasLPD; nodo: TNodo;
  flg_CalcularGradienteDeInversion: boolean; xFDV: TFuenteAleatoria;
  xNombreBorne: string; xfdisp: NReal; tRepHoras: NReal; xfSpeedUpMes: TDAofNReal;
  xfPerdidasInterferencias: NReal; xCurvaVelocidadPotencia_Pot: TFVectR;
  xPagoPorEnergiaEntregada_USD_MWh: NReal; xPagoPorEnergiaDisponible_USD_MWh: NReal;
  xflg_RestarParaPostizado: boolean; TonCO2xMWh: NReal;
  LowCostMustRun, CleanDevelopmentMechanism: boolean; xFuenteIdxP: TFuenteAleatoria;
  xBorneIdxP: string; fReservaRotante: NReal);
var
  i: integer;
begin
  inherited Create(capa, nombre, nacimiento, muerte, lpdUnidades, nodo,
    TonCO2xMWh, LowCostMustRun, CleanDevelopmentMechanism, xflg_RestarParaPostizado,
    flg_CalcularGradienteDeInversion,
    xFuenteIdxP, xBorneIdxP);

  fdisp := xfdisp;
  self.tRepHoras := tRepHoras;
  if length(xfSpeedUpMes) <> 12 then
    raise Exception.Create( exCantSpeedupParque_ +' ' + nombre + ' '+exDebeSer12_);
  fSpeedUpMes := xfSpeedUpMes;
  fPerdidasInterferencias := xfPerdidasInterferencias;
  SetLength(fComplexivoMes, 12);
  for i := 0 to high(fSpeedUpMes) do
    fComplexivoMes[i] := fSpeedUpMes[i] * fPerdidasInterferencias;

  CurvaVP := xCurvaVelocidadPotencia_Pot;
  FuenteDeViento := xFDV;
  NombreBorne := xNombreBorne;
  kBorneVelocidad := -1;
  PagoPorEnergiaEntregada_USD_MWh := xPagoPorEnergiaEntregada_USD_MWh;
  PagoPorEnergiaDisponible_USD_MWh := xPagoPorEnergiaDisponible_USD_MWh;

  fReserva := fReservaRotante;
end;

function TParqueEolico.Rec: TCosa_RecLnk;
begin
  Result := inherited Rec;
  Result.addCampoDef('fdisp', fdisp, 0, 7);
  Result.addCampoDef('fSpeedUp', px_fSpeedUp, 0, 7);
  Result.addCampoDef('fPerdidasInterferencias', fPerdidasInterferencias, 0, 7);
  Result.addCampoDef('CurvaVP', TCosa(CurvaVP), 0, 7);
  Result.addCampoDef_ref('fuenteDeViento', TCosa(fuenteDeViento), Self, 0, 7);
  Result.addCampoDef('nombreBorne', nombreBorne, 0, 7);
  Result.addCampoDef('fdisp', fdisp, 7, 14);
  Result.addCampoDef('fSpeedUpMes', fSpeedUPMes, 7, 14);
  Result.addCampoDef('fPerdidasInterferencias', fPerdidasInterferencias, 7, 14);
  Result.addCampoDef('CurvaVP', TCosa(CurvaVP), 7, 14);
  Result.addCampoDef_ref('fuenteDeViento', TCosa(fuenteDeViento), Self, 7, 14);
  Result.addCampoDef('nombreBorne', nombreBorne, 7, 14);
  Result.addCampoDef('fdisp', fdisp, 14);
  Result.addCampoDef('tRepHoras', tRepHoras, 14);
  Result.addCampoDef('fSpeedUpMes', fSpeedUPMes, 14);
  Result.addCampoDef('fPerdidasInterferencias', fPerdidasInterferencias, 14);
  Result.addCampoDef('CurvaVP', TCosa(CurvaVP), 14);
  Result.addCampoDef_ref('fuenteDeViento', TCosa(fuenteDeViento), Self, 14);
  Result.addCampoDef('nombreBorne', nombreBorne, 14);
  Result.addCampoDef('pagoPorEnergia', PagoPorEnergiaEntregada_USD_MWh, 36, 98);
  Result.addCampoDef('pagoPorPotenciaDisp', PagoPorEnergiaDisponible_USD_MWh, 36, 98);
  Result.addCampoDef('PagoPorDisponibilidad_USD_MWh',
    PagoPorEnergiaDisponible_USD_MWh, 98, 103);
  Result.addCampoDef('PagoPorEnergia_USD_MWh', PagoPorEnergiaEntregada_USD_MWh,
    98, 103);
  Result.addCampoDef('PagoPorEnergiaDisponible_USD_MWh',
    PagoPorEnergiaDisponible_USD_MWh, 103);
  Result.addCampoDef('PagoPorEnergiaEntregada_USD_MWh',
    PagoPorEnergiaEntregada_USD_MWh, 103);
  Result.addCampoDef('fReserva', fReserva, 192);
end;

procedure TParqueEolico.BeforeRead(version, id_hilo: integer);
begin
  inherited BeforeRead(version, id_hilo);
  PagoPorEnergiaDisponible_USD_MWh := 0;
  PagoPorEnergiaEntregada_USD_MWh := 0;
end;

procedure TParqueEolico.AfterRead(f: TArchiTexto);
var
  i: integer;
begin
  inherited AfterRead(f);
  if f.Version < 7 then
  begin
    SetLength(fSpeedUpMes, 12);
    SetLength(fComplexivoMes, 12);
    for i := 0 to high(fSpeedUpMes) do
    begin
      fSpeedUpMes[i] := px_fSpeedUp;
      fComplexivoMes[i] := fPerdidasInterferencias * px_fSpeedUP;
    end;
    kBorneVelocidad := -1;
    self.tRepHoras := 15 * 24;
  end
  else if f.Version < 14 then
  begin
    SetLength(fComplexivoMes, 12);
    for i := 0 to high(fSpeedUpMes) do
      fComplexivoMes[i] := fPerdidasInterferencias * fSpeedUPMes[i];
    kBorneVelocidad := -1;
    self.tRepHoras := 15 * 24;
  end
  else
  begin
    SetLength(fComplexivoMes, 12);
    for i := 0 to high(fSpeedUpMes) do
      fComplexivoMes[i] := fPerdidasInterferencias * fSpeedUPMes[i];
    kBorneVelocidad := -1;
  end;
end;


destructor TParqueEolico.destroy;
begin
  CurvaVP.Free;

  {$IFNDEF CALC_DEMANDA_NETA}
  SetLength(kBornePPA, 0);
  {$ENDIF}
  SetLength(fSpeedUpMes, 0);
  SetLength(fComplexivoMes, 0);
  SetLength(PPA, 0);

  inherited destroy;
end;


procedure TParqueEolico.PubliVars;
begin
  inherited PubliVars;
  PublicarVariableVR('PotenciaGenerable', '[MW]', 6, 2, PPA, True, True);
  PublicarVariableNR('VVel', '[m/s]', 6, 2, VVel, True);
  PublicarVariableNI('NMaqsDisponibles', '-', NMaquinasDisponibles, True);
end;



procedure TParqueEolico.PrepararMemoria(Catalogo: TCatalogoReferencias; globs: TGlobs);
var
  iposte: integer;
  auxf: TFRenR;
begin
  inherited prepararMemoria(Catalogo, globs);
  setlength(PPA, globs.NPostes);

  if not ((fuenteDeViento.durPasoDeSorteoEnHoras = 1) or
    ((fuenteDeViento.durPasoDeSorteoEnHoras = 0) and (globs.HorasDelPaso = 1))) then
    raise Exception.Create( mesFuenteAleatConPaso1h_+' ' + mesElParque_+' ' +
      nombre + ' '+ mesConectadoAFuente_ +' ' + fuenteDeViento.nombre +
      ' '+mesConPasoDeSorteo_+' = ' + IntToStr(fuenteDeViento.durPasoDeSorteoEnHoras));

  kBorneVelocidad := fuenteDeViento.idBorne(nombreBorne);
  {$IFDEF CALC_DEMANDA_NETA}
  auxf := TFf_xmult_conSelectorYBuffer.Create(capa, CurvaVP,
    fComplexivoMes, 1, @globs.MesInicioDelPaso, @PHoraria_PreSorteosPorUnidad,
    @globs.kSubPaso_);


  kBornePotencia := fuenteDeViento.registrarFuncion(auxf, nombreBorne, -1);

  // Atención, con esto inhibimos que la fuente haga el resumen y lo hace el generador.
  if fuenteDeViento.ResumirMaxVar(globs) <> nil then
  begin
    fuenteDeViento.ResumirMaxVar(globs).flg_ResumirBorneras := False;
    flg_ResumirPromediando := False;
  end
  else
    flg_ResumirPromediando := True;
  ;

  {$ELSE}
  SetLength(kBornePPA, globs.NPostes);
  for iposte := 0 to high(kBornePPA) do
  begin
     (*
     Para cada poste definimos un borne auxiliar que llevará la cuenta
     de la energía generada por el parque en ese poste y así puede calcular
     la potencia media equivalente del parque para ese poste.
     Se supone que la fuente de viento tiene paso de sorteo HORARIO.
     *)

    auxf := TFf_xmult_conselector.Create(capa, CurvaVP, fComplexivoMes,
      1, @globs.MesInicioDelPaso, iposte, @globs.kPosteHorasDelPaso,
      @globs.kSubPaso_);
    kBornePPA[iposte] := fuenteDeViento.registrarFuncion(auxf, nombreBorne, iposte);
  end;
  {$ENDIF}
end;


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

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

class function TParqueEolico.DescClase: string;
begin
  Result := rsParqueEolico;
end;


function TParqueEolico.get_pa_FD(kTipoUnidad: integer): NReal;
begin
  Result := fdisp;
end;

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


procedure TParqueEolico.SorteosDelPaso(sortear: boolean);
begin
  if globs.ObligarDisponibilidad_1_ then
    NMaquinasDisponibles := paUnidades.nUnidades_Operativas[0]
  else if (sortear) then
  begin
    ActualizarProbabilidadesReparacionYRotura_(fdisp, tRepHoras);
    NMaquinasDisponibles := Sorteos_RepRotUnidades;
  end
  else
    NMaquinasDisponibles := paUnidades.nUnidades_Operativas[0];
  inherited SorteosDelPaso(sortear);
end;

procedure TParqueEolico.PrepararPaso_ps;
var
  iposte: integer;
  jHora: integer;
begin
  inherited prepararPaso_ps;
  VVel := Self.fuenteDeViento.bornera[Self.kBorneVelocidad] *
    fSpeedUpMes[globs.MesInicioDelPaso - 1];
  {$IFNDEF CALC_DEMANDA_NETA}
  for iposte := 0 to high(PPA) do
    PPA[iposte] := Self.fuenteDeViento.bornera[Self.kBornePPA[iposte]] *
      NMaquinasDisponibles;
  {$ENDIF}

  flg_ReservaRotanteDisponible :=
     globs.flg_ReservaRotante and  ( fReserva > AsumaCero ) and  (NMaquinasDisponibles > 0);
end;


procedure TParqueEolico.opt_nvers(var ivar, ivae, ires: integer);
begin
  if NMaquinasDisponibles > 0 then
  begin
    self.ivar := ivar;
    self.ivae := ivae;
    self.ires := ires;
    ivar := ivar + globs.NPostes;

    if flg_ReservaRotanteDisponible then
    begin
      ivar_ReservaRotante := ivar;
      ivar := ivar + globs.NPostes;
      ires_ReservaRotante := ires;
      ires := ires + 2 * globs.NPostes;
    end;

  end;

end;

function TParqueEolico.getNombreVar(ivar: integer; var nombre: string): boolean;
begin
  if NMaquinasDisponibles > 0 then
  begin

    if flg_ReservaRotanteDisponible
       and (ivar >= self.ivar_ReservaRotante) and
      (ivar < self.ivar_ReservaRotante + globs.NPostes) then
    begin
      nombre := self.Nombre + '_RR[MW]' + IntToStr(ivar - self.ivar_ReservaRotante + 1);
      Result := True;
      exit;
    end;

    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
    begin
       Result := false;
    end;
  end
  else
    Result := False;
end;

function TParqueEolico.getNombreRes(ires: integer; var nombre: string): boolean;
begin
  if flg_ReservaRotanteDisponible
     and ( ires >= ires_ReservaRotante )
     and ( ires < ires_ReservaRotante + 2 * globs.NPostes  ) then
    begin
      if ires <   ires_ReservaRotante +  globs.NPostes then
        nombre:= self.nombre+ '_ReservaRot_LimPot_P_'+ IntToStr( ires - ires_ReservaRotante + 1 )
      else
        nombre:= self.nombre+ '_ReservaRot_GenPos_P_'+ IntToStr( ires - ires_ReservaRotante + 1 );
      result:= true;
      exit;
    end
  else
    result:= false;
end;

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


procedure TParqueEolico.opt_cargue(s: TSimplex);
var
  iposte: integer;
begin
  if NMaquinasDisponibles = 0 then
    exit;

  for iposte := 0 to globs.NPostes - 1 do
    s.pon_e(nodo.ires + iposte, ivar + iposte, 1);


  if flg_ReservaRotanteDisponible then
  begin
    for iposte := 0 to globs.NPostes - 1 do
    begin
      s.pon_e(nodo.ires_ReservaRotante + iposte, ivar_ReservaRotante + iposte, 1);
      s.pon_e(s.nf, ivar_ReservaRotante + iposte, 0);

      s.pon_e(ires_ReservaRotante + iposte, ivar + iposte, -1);// potencia
      s.pon_e(ires_ReservaRotante + iposte, ivar_ReservaRotante + iposte, -1);// reserva
      s.pon_e(ires_ReservaRotante + iposte, s.nc, PPA[iPoste]);// termino independiente

      s.pon_e(ires_ReservaRotante + globs.NPostes + iposte, ivar + iposte, 100);
      // potencia
      s.pon_e(ires_ReservaRotante + globs.NPostes + iposte,
        ivar_ReservaRotante + iposte, -1);
    end;

  end;

end;

procedure TParqueEolico.opt_fijarRestriccionesDeCaja(s: TSimplex);
var
  iposte: integer;
begin
  if NMaquinasDisponibles = 0 then
    exit;
  // Le fijamos como cota superior PMax a la potencia en todos los postes
  for iposte := 0 to globs.NPostes - 1 do
    if PPA[iposte] > AsumaCero then
      s.cota_sup_set(ivar + iposte, PPA[iposte])
    else
      s.FijarVariable(ivar + iposte, 0);


  if flg_ReservaRotanteDisponible then
    //si el modo reserva esta activado agrego la restriccion de caja que le pone
    //una cota superior a la reserva a despachar. Dicha cota es igual al producto
    //de la potencia maxima que puede ser despachada con el factor de reserva
  begin
    for iposte := 0 to globs.NPostes - 1 do
      if PPA[iposte] > AsumaCero then
        s.cota_sup_set(ivar_ReservaRotante + iposte, PPA[iposte] * fReserva)
      else
        s.FijarVariable(ivar_ReservaRotante + iposte, 0);

  end;

end;

procedure TParqueEolico.opt_leerSolucion(s: TSimplex);
var
  iposte: integer;
  e, eacum, disp: NReal;
begin
  costoDirectoDelPaso := 0;
  if NMaquinasDisponibles = 0 then
  begin
    vclear(P);

    if globs.flg_ReservaRotante then
      vclear(ReservaRotante);

    //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;
    Ingreso_PorEnergia_ := 0;
    exit;
  end;

  // recuperamos los valores de Potencia despachada
  eacum := 0;
  disp := 0;
  for iposte := 0 to globs.NPostes - 1 do
  begin
    e := s.xval(ivar + iposte);
    P[iposte] := e;
    Lambda_P[iPoste] := s.xmult(ivar + iposte) / globs.DurPos[iposte];
    cv_Spot[iPoste] := Nodo.cmarg[iposte] - Lambda_P[iPoste];
    eacum := eacum + e * globs.DurPos[iposte];
    disp := disp + PPA[iposte] * globs.DurPos[iposte];
  end;


  if flg_ReservaRotanteDisponible then
    for iposte := 0 to globs.NPostes - 1 do
      ReservaRotante[iposte] := s.xval(ivar_ReservaRotante + iposte);

  Ingreso_PorDisponibilidad_ := disp * PagoPorEnergiaDisponible_USD_MWh;
  Ingreso_PorEnergia_ := eacum * PagoPorEnergiaEntregada_USD_MWh;

end;


procedure TParqueEolico.dump_Variables(var f: TextFile; charIndentacion: char);
var
  i: integer;
begin
  inherited dump_Variables(f, charIndentacion);
  writeln(f, charIndentacion, 'VVel[m/s]= ', FloatToStrF(VVel, ffFixed, 10, 3));
  for i := 0 to high(PPA) do
    writeln(f, charIndentacion, 'PPA[', i, '][MW]: ',
      FloatToStrF(PPA[i], ffFixed, 10, 3));
  writeln(f);
  //  writeln(f, charIndentacion, 'PotMediaGenerable[MW]= ', FloatToStrF(PotMediaGenerable, ffFixed, 10, 3));
  writeln(f, charIndentacion, 'NMaquinasDisponibles= ', NMaquinasDisponibles);
  writeln(f);
end;

procedure AlInicio;
begin
  registrarClaseDeCosa(TParqueEolico.ClassName, TParqueEolico);
end;

procedure AlFinal;
begin
end;

end.
