unit uoddface_pam;

{$mode delphi}

interface

uses
  Classes, SysUtils,
  uoddface,
  xmatdefs,
  matreal,
  uauxiliares,
  uactores,
  ufechas,
  usalasdejuego,
  uDataSetGenerico,
  urosx,
  uInicioYFinal,
  uconstantessimsee;


(*

===========================================================
      P A M  Optimizador del Plan Anual de Mantenimiento
============================================================

Se trata de determinar el PAM (Plan Anual de Mantenimientos) de las centrales
de generación.
Para ello se debe crear la Sala SimSEE que con el sistema a optimizar.
Utilizando la aplicación oddface_prepare se debe crear el problema de optmización,
subir la sala y editar en la información del problema el listado de ordenes de mantenimiento
indicando el nombre de la central (tiene que coincidir con el Actor SimSEE)
y indicar la cantidad de unidades a poner en mantenimiento, la cantidad de días
que durará el mantenimiento y una fecha de antes de la cual NO se debe iniciar el
mantenimieno y una fecha antes de la cual SE debe iniciar el mantenimiento.

El parámetro de otimización, en cada orden es la fecha de inicio del mantenimiento
dentro de la ventana definida como posible para su inicio.

*)


type
  TPAM_OrdenDeMantenimiento = class
    nid: integer;
    nombre: string; // Nombre de la máquina en SimSEE
    unidades_a_mantener: integer; // Unidades a poner en  mantenimiento
    dias_parada: integer;
    fecha_inicio: TFecha; // Primer fecha a considerar
    fecha_fin: TFecha; // Última fecha a considerar
    class function ReadFromRec( var r: TDataRecord ): TPAM_OrdenDeMantenimiento;
    procedure Free;
  end;

  TPAM_OrdenesDeMantenimiento = class
    lst: TList;
  private
    function getOrden( index: integer ): TPAM_OrdenDeMantenimiento;
    procedure setOrden( index: integer; value: TPAM_OrdenDeMantenimiento );

  public
    class function ReadFromDB(dbconx: TDBrosxCon; nid_problema: integer
      ): TPAM_OrdenesDeMantenimiento;
    procedure Free;
    property orden[ index: integer ]: TPAM_OrdenDeMantenimiento
             read getOrden write setOrden; Default;
  end;


  TSimCostos_archi = class
    tasa: NReal; // [p.u.] tasa de descuento en por unidad
    cad_VE: NReal; // [MUSD] valor esperado del CAD
    cad_VaR5pe: NReal; // [MUSD] VaR(5%) del CAD
    cad_VE_aux: NReal; // [MUSD] valor esperado del CAD considerando CF_aux al final
    cad_VaR5pe_aux: NReal; // [MUSD] VaR(5%) del CAD considerando CF_aux al final
    vcad: TDAOfNReal; // [MUSD] vector con los CAD de cada crónica
    vcad_aux: TDAOfNReal; // [MUSD] vector de los CAD de cada crónica, considerando CF_aux al final
    vcdp: TDAOfNReal; // [MUSD] vector con los costos directos del paso (CDP) acumulados de cada crónica.
    vcffja: TDAOfNReal; // [MUSD] vector con el Costo Futuro de Fin de Juego Actualizado de cad crónica.
    vcffja_aux: TDAOfNReal; // [MUSD] vector con el Costo Futuro (auxiliar) de Fin de Juego Actualizado de cad crónica.
    constructor CreateRead( archi_res: string; NCronicas: integer );
    procedure Free;
  end;


  { TPAM_Problema }

  TPAM_Problema = class( TProblema )
    CarpetaSala: string;
    NombreSala: string;

    Ordenes: TPAM_OrdenesDeMantenimiento;

    constructor Create(dbconx: TDBrosxCon; recProblema: TDataRecord; idEjecutor_: integer; tmp_rundir_: string );
    class function CreateFromDB(dbconx: TDBrosxCon; nid_problema: integer;
      idEjecutor_: integer; tmp_rundir_: string): TPAM_Problema;
    function evaluar_( Individuo: TIndividuo; SemillaAleatoria: integer ): boolean; override;
    procedure Free; override;

    function RunOptSim( sala: string; NCronicas, semillaAleatoria_: integer ): TSimCostos_archi;

    // Crea la sala y retorna el camino completo a la misma.
     function CrearSalaSimSEE( individuo: TIndividuo ): string;
     function BajarCrearSalaSimSEE( nid: integer ): string;
  end;




implementation


(** Constructor de un problema tipo PAM **)
constructor TPAM_Problema.Create(dbconx: TDBrosxCon; recProblema: TDataRecord;
  idEjecutor_: integer; tmp_rundir_: string);

var
  kEtapa: integer; // Ordinal de la etapa de decisión
  kOrden: integer; // Ordinal de la Orden (del listado especificado en el problema)
  (*** En la forma de codificación del problema que se implementó, cada parámetro
  de optimización será el tiempo en el cual se comienza la realización de la orden.
  Como en problema se resuelve en una ventana de tiempo que se dividió en etapas,
  el parámetro será el ordinal de la etapa en que comienza la tarea. ***)

  aOrden: TPAM_OrdenDeMantenimiento; // Auxiliar para ir recorriendo las órdenes.

  rDias: double; // Distancia en días para calcular k_Min y k_Max
  k_Min, k_Max: integer; // Ordinales mínimo y máximo de la etapa en que puede comenzar la orden analizada

begin
  // LLamamos al método de la clase ancestral para que se cree lo que se tenga que crear.
  inherited Create( dbconx,  recProblema, idEjecutor_, tmp_rundir_ );


// calculamos carpeta de la sala y nombre
//  CarpetaSala := extractFilePath( ArchiSala );
  CarpetaSala:= tmp_rundir_;
  NombreSala:= extractFileName( ArchiSala );
// acomodamos el directorio de la sala
  ArchiSala:= tmp_rundir_ + DirectorySeparator + NombreSala;
  if ( pos( '.ese', NombreSala ) > 0 ) then
     delete( NombreSala, pos( '.ese', NombreSala ), length( NombreSala ) - pos( '.ese', NombreSala ) + 1 );


// cargamos las ordenes de mantenimiento a optimizar
  ordenes:= TPAM_OrdenesDeMantenimiento.ReadFromDB( dbconx, nid_Problema );
  if ordenes = nil then
     raise Exception.Create('No pude leer archivo del problema.');

  // Bien, ahora que tenemos las ordenes  definimos los parámetros
  // Los problemas tienen juegos de parámetros Enteros y Reales.
  // En este caso solo usaremos los enteroes. (DescriptoresE).
  setlength( DescriptoresE, ordenes.lst.count );
  setlength( DescriptoresR, 0 );

   for kOrden:= 0 to ordenes.lst.count-1 do
   begin
      aOrden:= ordenes[kOrden];

      // determinamos la primer etapa posible.
      // rdias = dias desde la primer fecha de inicio según la especificación global del
      // problema y la primer fecha de de inicio especificada en la orden.
      rdias:= aOrden.fecha_inicio.dt - fecha_primer_etapa.dt;
      if ( rdias < 0 ) then // mmm no está bueno el mantenimient puede empesar antes
        k_min:= 0 // Limita la caja del problema
      else
        k_min:= trunc( (rdias +0.9) / dias_por_etapa ); // limita la caja de la orden
      if k_min > self.n_etapas then
         raise Exception.Create('Problema Infactible, una de las ordenes no puede iniciarse en la ventana del problema ');


      // ahora determinamos la última etapa posible
      // rdias = dias desde primer fehca según problema y última fecha según la orden.
      rdias:= aOrden.fecha_fin.dt - fecha_primer_etapa.dt;
      if ( rdias < 0 ) then
      begin
        k_max:= 0; // mmm no es posible solucionar este problema. ERROR!!! INFACTIBLE DE PLANO!.
        raise Exception.Create('Problema Infactible, una de las ordenes no puede iniciarse en la ventana del problema ');
      end
      else
        k_max:= trunc( (rdias +0.9) / dias_por_etapa );
      if k_max > self.n_etapas then k_max:= n_etapas;

      DefinirParametroEntero(
        kOrden,
        'e_'+aOrden.Nombre+'_'+IntToStr( aOrden.nid ),
        k_min, k_max  );
   end;

  CalcularLargosADN;
end;


class function TPAM_Problema.CreateFromDB(dbconx: TDBrosxCon; nid_problema: integer;  idEjecutor_: integer; tmp_rundir_: string ): TPAM_Problema;
var
  ds: TResultadoQuery;
  r: TDataRecord;
  ap: TPAM_Problema;
begin
  ds:= dbconx.sql_query( 'SELECT * FROM ofe_problemas WHERE nid = '+IntToStr( nid_problema ) +' LIMIT 1 ');
  r:= ds.next;
  if r <> nil then
    ap:= TPAM_Problema.Create(dbconx, r, idEjecutor_, tmp_rundir_ )
  else
    ap:= nil;
  ds.Free;
  result:= ap;
end;


function nextpal_sep(var r: string; sep: string ): string;
var
  res: string;
  i:   integer;
begin
  r := trim(r);
  i := pos( sep, r);
  if i > 0 then
  begin
    res := trim(copy(r, 1, i - 1));
    Delete(r, 1, i);
  end
  else
  begin
    res := r;
    r   := '';
  end;
  Result := res;
end;


function nextpal_tab(var r: string): string;
begin
  result:= nextpal_sep( r, #9 );
end;





procedure readln_vect( var f: textfile; var v: TDAOfNreal; N: integer );
var
  r: string;
  pal: string;
  k: integer;
begin
  setlength( v, N );
  system.readln( f, r );
  pal:= nextpal_tab( r ); // caption
  for k:= 0 to high( v ) do
  begin
    pal:= nextpal_tab( r );
    v[k]:= StrToFloat( pal );
  end;
end;



class function TPAM_OrdenDeMantenimiento.ReadFromRec( var r: TDataRecord ): TPAM_OrdenDeMantenimiento;
var
 res: TPAM_OrdenDeMantenimiento;
begin
  res:= TPAM_OrdenDeMantenimiento.Create;
  res.nid:= r.GetByNameAsInt( 'nid' );
  res.nombre:= r.GetByNameAsString( 'nombre' );
  res.unidades_a_mantener:= r.GetByNameAsInt( 'unidades_a_mantener' );
  res.dias_parada:= r.GetByNameAsInt( 'dias_parada' );
  res.fecha_inicio:= TFecha.Create_ISOStr( r.GetByNameAsString( 'fecha_inicio' ) );
  res.fecha_fin:= TFecha.Create_ISOStr( r.GetByNameAsString( 'fecha_fin' ) );
  result:= res;
end;



procedure TPAM_OrdenDeMantenimiento.Free;
begin
  fecha_inicio.Free;
  fecha_fin.Free;
  inherited Free;
end;



class function TPAM_OrdenesDeMantenimiento.ReadFromDB(
  dbconx: TDBrosxCon; nid_problema: integer ): TPAM_OrdenesDeMantenimiento;
var
  aTecno: TPAM_OrdenDeMantenimiento;
  res: TPAM_OrdenesDeMantenimiento;
  sql: string;

  ds: TResultadoQuery;
  rec: TDataRecord;

begin
  res:= TPAM_OrdenesDeMantenimiento.Create;
  res.lst:= TList.Create;

  sql := 'SELECT * FROM ofe_PAM_ordenes WHERE nid_problema = ' + IntToStr(NID_Problema) +' AND activa = 1 ORDER BY nid ';
  ds:= dbconx.sql_query(sql);
  rec := ds.Next;
  while rec <> nil do
  begin
    aTecno:= TPAM_OrdenDeMantenimiento.ReadFromRec( rec );
    res.lst.add( aTecno );
    rec:= ds.Next;
  end;

  ds.Free;
  result:= res;
end;



procedure TPAM_OrdenesDeMantenimiento.Free;
var
  k: integer;
begin
  for k:= 0 to lst.count-1 do
    orden[k].Free;
  lst.Free;
  inherited Free;
end;

function TPAM_OrdenesDeMantenimiento.getOrden( index: integer ): TPAM_OrdenDeMantenimiento;
begin
  result:= lst.items[ index ];
end;

procedure TPAM_OrdenesDeMantenimiento.setOrden( index: integer; value: TPAM_OrdenDeMantenimiento );
begin
  lst.items[ index ]:= value;
end;



constructor TSimCostos_archi.CreateRead( archi_res: string; NCronicas: integer );
var
  f: textfile;
begin
  inherited Create;

  assign( f, archi_res );
  {$I-}
  reset( f );
  {$I+}
  if ioresult <> 0 then
   raise Exception.Create( 'No encontré archivo: '+archi_res);

  system.readln( f, tasa );
  system.readln( f, cad_VE);
  system.readln( f, cad_VaR5pe );
  system.readln( f, cad_VE_aux );
  system.readln( f, cad_VaR5pe_aux );

  readln_vect( f, vcad, NCronicas );
  readln_vect( f, vcad_aux, NCronicas );
  readln_vect( f, vcdp, NCronicas );
  readln_vect( f, vcffja, NCronicas );
  readln_vect( f, vcffja_aux, NCronicas );

  closefile( f );
end;

procedure TSimCostos_archi.Free;
begin
  setlength( vcad, 0 );
  setlength( vcad_aux, 0 );
  setlength( vcdp, 0 );
  setlength( vcffja, 0 );
  setlength( vcffja_aux, 0 );
  inherited Free;
end;


function TPAM_Problema.CrearSalaSimSEE( individuo: TIndividuo ): string;
var
  sala: TSalaDejuego;
  kEtapa, kOrden: integer;
  actor: TActor;
  Orden: TPAM_OrdenDeMantenimiento;
  nUnidades: TDAofNInt;
  fecha: TFecha;
  archiSalaModificada: string;

{$IFDEF DEBUG_CrearSalaSimSEE}
  s: string;
{$ENDIF}

begin
  uInicioYFinal.AlInicio;

  // cargamos la sala de juegos_base

  chdir(carpetaSala);

  sala:= TSalaDeJuego.cargarSala(0, archiSala, '__principal__', true );
  setlength( nUnidades, 1 );

(****

 Una posible es que los parámeros sean, indicar de cada
 cuantas unidades se quieren poner en mantenimiento en la ventana de tiempo
 y que el itente ubicarlas
 por ej,
 Mantener( 2 unidades de Salto grande sacandolas 15 días en la ventana 2013-01-01 a 2014-01-01 )

 Para mentener las 7 unidades de uruguay la orden sería poner varios mantenimientos
 para la misma central y que entonces pueda agruparlas o no.

 Las CTR, por ejemplo podría ser
 mantener( ctr, 1, 45d, fecha1, fecha2 )
 mantener( ctr, 1, 30d, fecha1, fecha2 )


 ***)

  for kOrden:= 0 to ordenes.lst.count - 1 do
  begin
    orden:= ordenes[kOrden];
    actor:= sala.ListaActores.find( Orden.Nombre ) as TActor;

    {$IFDEF DEBUG_CrearSalaSimSEE}
    s:= IntToStr( kOrden );
    {$ENDIF}

    kEtapa:= Individuo.XE.e( kOrden + 1 );
    fecha:= TFecha.Create_Clone( fecha_primer_etapa );
    fecha.addDias( kEtapa * dias_por_etapa );
    nUnidades[0]:= - Orden.unidades_a_mantener;

    actor.lpdUnidades.delta_unidades_( fecha, nUnidades );

    {$IFDEF DEBUG_CrearSalaSimSEE}
    s:= s+', '+Orden.Nombre+ ',du: '+ IntToStr( Orden.unidades_a_mantener )+', '+fecha.AsISOStr+', '+ IntToStr( nUnidades[0] );
    {$ENDIF}

//    kEtapa := kEtapa + trunc( orden.dias_parada / dias_por_etapa +0.2 );
    fecha:= TFecha.Create_Clone( fecha_primer_etapa );
//    fecha.addDias( kEtapa * dias_por_etapa );
    fecha.addDias( kEtapa * dias_por_etapa +  orden.dias_parada );
    nUnidades[0]:= Orden.unidades_a_mantener;
    actor.lpdUnidades.delta_unidades_( fecha, nUnidades );

    {$IFDEF DEBUG_CrearSalaSimSEE}
     s:= s+', '+fecha.AsISOStr+', '+ IntToStr( nUnidades[0] );
     writeln( s );
    {$ENDIF}
  end;

  archiSalaModificada:= getDir_run + DirectorySeparator + self.NombreSala + '_oddface_.ese';
  sala.WriteToArchi( archiSalaModificada );
  sala.Free;

  uInicioYFinal.AlFinal;

  result:=  archiSalaModificada;
end;

function TPAM_Problema.BajarCrearSalaSimSEE( nid: integer ): string;
var
  a: TIndividuo;
begin
  a:= LeerIndividuo( nid );
  decodificar_adn( a );
  result:= CrearSalaSimSEE( a );
end;



// retorna NIL si falla algo
function TPAM_Problema.RunOptSim( sala: string; NCronicas, semillaAleatoria_: integer ): TSimCostos_archi;
var
  cmd:    string;
  params: array of string;
  archi_simcosto: string;
begin

  if tmp_rundir = '' then
    archi_simcosto:= getDir_run + NombreSala + DirectorySeparator
  else
    archi_simcosto := getDir_run;

 limpiarCarpeta( archi_simcosto, 'simres_*' );
 limpiarCarpeta( archi_simcosto, 'simcosto_*' );
 limpiarCarpeta( archi_simcosto, 'estado_fin_cron_*' );


  setlength(params, 3);
  params[0] := 'sala=' + sala;
  params[1] := 'ejecutor=' + IntToStr( idEjecutor );
  params[2] := 'semilla='+ IntToStr( semillaAleatoria_ );

{$IFDEF LINUX}
  cmd := getDir_bin + 'cmdopt';
{$ELSE}
  cmd := getDir_bin + 'cmdopt.exe';
{$ENDIF}
  if not RunChildAndWAIT(cmd, params) then
    raise Exception.Create('no puede correr la cmdopt');
{$IFDEF LINUX}
  cmd := getDir_bin + 'cmdsim';
{$ELSE}
  cmd := getDir_bin + 'cmdsim.exe';
{$ENDIF}
  setlength(params, 4);
  params[0] := 'sala=' + sala;
  params[1] := 'ejecutor=' + intToStr( idEjecutor );
  params[2] := 'semilla='+ IntToStr( semillaAleatoria_ );
  params[3] := 'NCronicasSim=' + IntToStr( NCronicas );

  if not RunChildAndWAIT(cmd, params) then
    raise Exception.Create('no puede correr la cmdsim');
  setlength(params, 0);


  try
    if tmp_rundir = '' then
      archi_simcosto:= getDir_run + NombreSala + DirectorySeparator
    else
      archi_simcosto := getDir_run;

    archi_simcosto:= archi_simcosto+ DirectorySeparator + 'simcosto_' + IntToStr( semillaAleatoria_ ) + 'x'+IntToStr( NCronicas )+'_base.xlt';
    result:= TSimCostos_archi.CreateRead( archi_simcosto, NCronicas );
  except
    result:= nil;
    raise Exception.Create('Error al leer archivo de resultados uplanes.425: ' + archi_simcosto );
  end;

end;

function TPAM_Problema.evaluar_( Individuo: TIndividuo; SemillaAleatoria: integer  ): boolean;
var
  archiSalaModificada: string;
  res_SimCostos: TSimCostos_archi;

begin
  archiSalaModificada:= crearSalaSimsee( Individuo );
  result:= true;
  res_SimCostos:= RunOptSim( archiSalaModificada, NCronicasCronicasPorVez, SemillaAleatoria );
  if res_simCostos <> nil then
  begin
  Individuo.f_histo:= TVectR.Create_FromDAofR( res_SimCostos.vcad );
  Individuo.f_VE:= res_SimCostos.cad_VE;
  Individuo.f_VaR:= Individuo.f_histo.pe_VaR(  pe_var );
  Individuo.f_CVaR:= Individuo.f_histo.pe_CVaR(  pe_var );
  Individuo.f_MIN:= individuo.f_histo.e(1);
  Individuo.f_MAX:= individuo.f_histo.e( individuo.f_histo.n );
  Individuo.f_Objetivo:= ro_VE* Individuo.f_VE + ro_VaR * Individuo.f_VaR + ro_CVaR * Individuo.f_CVaR;
  res_SimCostos.Free;
  end
  else
    raise Exception.Create('Falló la simulación');
end;


procedure TPAM_Problema.Free;
begin
  Ordenes.Free;
  inherited Free;
end;

end.

