{xDEFINE CHEQUEOMEM}
{$DEFINE SPXMEJORCAMINO}
{$DEFINE ESTABILIZAR_FRAMEINICIAL}
unit umh_distribuidor_optsim;

{
rch@291310050943

Me propongo reescribir el MultiHilo haciendo que se creen tanttos workers
como hilos de cálculo se quiera y que el hilo principal se encarge de
administrar las tareas.

}
{$MODE Delphi}
interface

uses
{$IFDEF CHEQUEOMEM}
  udbgutil,
{$ENDIF}
  Classes, SysUtils,
  uglobs, Math,
  uconstantesSimSEE,
  xmatdefs,
 {$IFDEF SPXMEJORCAMINO}
  umipsimplex_mejorcamino,
 {$ELSE}
  umipsimplex,
 {$ENDIF}
  winLinuxUtils,
  uAuxiliares,
  usalasdejuego,
  uacumuladores_sim,
  uInterpreteDeParametros,
  uversiones, umh_sincrodata;

type
  {
  TGestorSalaMH esta clase es utilizada para dar "multiplicidad" a una sala para el procesamiento
  en paralelo en varios hilos.
  }
  TGestorSalaMH = class
  private
  public
    sala: TSalaDeJuego;
    sincrodata_mh: TSincroData_mh;

    nCores: integer;                    //cantidad de Nucleos en el equipo.
    //Por defecto es 1

    nHilosForzados: integer;
    //-1 significa libre, otro valor la cantidad de hilos a usar

    nTareasForzadas: integer;
    //-1 significa libre, otro valor la cantidad de tareas a calcular por paso


    nTareas: integer;                   //Número de particiones del frame
    nHilos: integer;                    //Número de hilos realizando los cálculos

    constructor Create(sala: TSalaDeJuego); { Guarda el puntero a la sala y obtiene la cantidad de núcles en el sistema.
      Pero no hace más nada }

    procedure sim_inicializarVariablesMultiCore(NCronicas, NEstados: integer);
    procedure sim_CrearTareas(kCronicaIni, kCronicaFin, kEstadoIni,
      kEstadoFin: integer);

    destructor destroy; override;

    procedure liberarVariablesMultiCore;

    // Retorna el valor esperado del costo.
    function sim_SimularMultiCore(nHilosForzados: integer): NReal;

    procedure opt_darPaso;
    procedure opt_inicializarVariablesMultiCore;
    procedure opt_CrearTareas(estrellaIni, estrellaFin: integer);

    // poner nHilosForzados = -1 si se quiere igual cantidad de hilos que CORES del PC
    procedure opt_OptimizarMultiCore(nHilosForzados: integer);
    procedure AplicarParametros(aInterpreteDeParametros: TInterpreteParametros);
  end;

implementation

uses
  umh_worker_opt, umh_worker_sim;

constructor TGestorSalaMH.Create(sala: TSalaDeJuego);
begin
  inherited Create;
  self.sala := sala;
  self.nCores := winLinuxUtils.GetSystemCoreCount;
  self.nHilosForzados := -1;
  self.nHilos := 1;
  self.nTareasForzadas := -1;
  self.nTareas := 1;
  sincrodata_mh := nil;
end;



procedure TGestorSalaMH.opt_darPaso;
begin
  sala.globs.SwapAuxs;
  sala.globs.Fijar_kPaso(sala.globs.kPaso_Opt - 1);
end;

procedure TGestorSalaMH.opt_inicializarVariablesMultiCore;
var
  i: integer;
  robot: TRobotOptRangoEstrellas;
  flg_Habilitado: boolean;

begin

  if nHilosForzados = -1 then
    nHilos := nCores
  else
    nHilos := nHilosForzados;

  if nTareasForzadas <> -1 then
    nTareas := nTareasForzadas
  else
    nTareas := nHilos;

  sincrodata_mh := TSincroData_mh.Create(nTareas, nHilos);

  sala.globs.Alerta('Optimización multihilo, Nucleos: ' + IntToStr(
    nCores) + ', Hilos: ' + IntToStr(nHilos));

  if sala.globs.procAlertaHabilitado then
  begin
    sala.globs.deshabilitarAlertas;
    flg_Habilitado := True;
  end
  else
    flg_Habilitado := False;

  sincrodata_mh.paso := sala.globs.NPasos + 1;

  for i := 0 to high(sincrodata_mh.robots) do
  begin
    robot := TRobotOptRangoEstrellas.Create(i, sincrodata_mh, sala);
    if Assigned(Robot.FatalException) then
      raise Robot.FatalException;
    sincrodata_mh.robots[i] := robot;
  end;

  if flg_Habilitado then
    sala.globs.habilitarAlertas;
end;

procedure TGestorSalaMH.sim_inicializarVariablesMultiCore(NCronicas,
  NEstados: integer);
var
  i, NTrayectorias: integer;
  robot: TRobotSimRangoCronicas;
begin
  if nHilosForzados = -1 then
    nHilos := nCores
  else
    nHilos := nHilosForzados;

  if nTareasForzadas <> -1 then
    nTareas := nTareasForzadas
  else
    nTareas := nHilos;

  NTrayectorias:= NCronicas * NEstados;

  nTareas := min(NTrayectorias, nTareas);
  nHilos := min(nHilos, nTareas);

  {$IFDEF VERBOSO}
  writeln('sim_inicializarVariablesMultiCore');
  writeln('nTareas: ', nTareas, ', nHilos: ', nHilos);
  {$ENDIF}

  sincrodata_mh := TSincroData_mh.Create(nTareas, nHilos);
  sala.globs.Alerta('Simulación multihilo, Nucleos: ' + IntToStr(
    nCores) + ', Hilos: ' + IntToStr(nHilos));
  sala.globs.deshabilitarAlertas;

  // apuntamos el procedimiento de notificación de fin de crónica.
  {$IFDEF SIM_MULTIHILO_NOTIFICANDO_GUI}
  umh_worker_sim.mh_SimFinCronica:= sala.globs.procNot_FinCronica;
  {$ENDIF}


  sincrodata_mh.paso := 1 + 1;

  for i := 0 to high(sincrodata_mh.robots) do
  begin
    robot := TRobotSimRangoCronicas.Create(i, sincrodata_mh, sala);
    if Assigned(Robot.FatalException) then
      raise Robot.FatalException;
    sincrodata_mh.robots[i] := robot;
  end;
  sala.globs.habilitarAlertas;
end;


procedure TGestorSalaMH.liberarVariablesMultiCore;
begin
  sincrodata_mh.Free;
  sincrodata_mh:= nil;
end;

procedure TGestorSalaMH.AplicarParametros(
  aInterpreteDeParametros: TInterpreteParametros);
begin
  // si no está definido vamos a monohilo
  nHilosForzados := aInterpreteDeParametros.valInt('nhilos', 1);
  nTareasForzadas := aInterpreteDeParametros.valInt('ntareas', 1);
end;

procedure TGestorSalaMH.opt_CrearTareas(estrellaIni, estrellaFin: integer);
var
  i, iEstrIni, iEstrFin: integer;
  nEstrellas, nEstrellasPorTarea, nTareasConUnaEstrellaMas: integer;
begin
  nEstrellas := estrellaFin - estrellaIni + 1;

  nEstrellasPorTarea := nEstrellas div nTareas;
  nTareasConUnaEstrellaMas := nEstrellas mod nTareas;

  setlength(sincrodata_mh.tareas, nTareas);
  iEstrIni := estrellaIni;
  for i := 0 to nTareasConUnaEstrellaMas - 1 do
  begin
    iEstrFin := iEstrIni + nEstrellasPorTarea;
    sincrodata_mh.tareas[i] :=
      TTareaCalcularRangoEstrellas.Create(i, iEstrIni, iEstrFin);
    iEstrIni := iEstrFin + 1;
  end;

  for i := nTareasConUnaEstrellaMas to nTareas - 1 do
  begin
    iEstrFin := iEstrIni + nEstrellasPorTarea - 1;
    sincrodata_mh.tareas[i] :=
      TTareaCalcularRangoEstrellas.Create(i, iEstrIni, iEstrFin);
    iEstrIni := iEstrFin + 1;
  end;

end;

procedure TGestorSalaMH.sim_CrearTareas(kCronicaIni, kCronicaFin, kEstadoIni,
  kEstadoFin: integer);
var
  nCronicas, nEstados,
    nTrayectoriasPorTarea, nTareasConUnaTrayectoriaMas,
    kTarea, iTrayectoriaFin, iTrayectoriaIni, nTrayectorias: integer;
begin

  nCronicas := kCronicaFin - kCronicaIni + 1;
  nEstados := kEstadoFin - kEstadoIni + 1;

  nTrayectorias:= nCronicas * nEstados;

  nTrayectoriasPorTarea := nTrayectorias div nTareas;
  nTareasConUnaTrayectoriaMas := nTrayectorias mod nTareas;

  setlength(sincrodata_mh.tareas, nTareas);

  for kTarea := 0 to nTareasConUnaTrayectoriaMas - 1 do
  begin
    iTrayectoriaIni:= kTarea * ( nTrayectoriasPorTarea + 1);

    iTrayectoriaFin := iTrayectoriaIni + nTrayectoriasPorTarea;

    sincrodata_mh.tareas[kTarea] :=
      TTareaSimRangoCronicas.Create(kTarea,iTrayectoriaIni + 1, iTrayectoriaFin + 1);
  end;

  for kTarea := nTareasConUnaTrayectoriaMas to nTareas - 1 do
  begin

    iTrayectoriaIni:= kTarea * nTrayectoriasPorTarea + nTareasConUnaTrayectoriaMas;

    iTrayectoriaFin := iTrayectoriaIni + nTrayectoriasPorTarea -1;

    sincrodata_mh.tareas[kTarea] :=
      TTareaSimRangoCronicas.Create(kTarea, iTrayectoriaIni + 1, iTrayectoriaFin + 1);
  end;
end;

destructor TGestorSalaMH.destroy;
begin
  sincrodata_mh.Free;
  inherited destroy;
end;




procedure TGestorSalaMH.opt_OptimizarMultiCore(nHilosForzados: integer);
var
  dtIni, dtPaso: TDateTime;
  kPasoIni: integer;
  segundos_transcurridos, segundos_restantes: NReal;

  {$IFDEF ESTABILIZAR_FRAMEINICIAL}
const
  MAX_CANT_ITERS = 100;
var
  cntIters: integer;
  stop: boolean;
  err: NReal;
  err_max_relativo: NReal;
  {$ENDIF}

begin
  dtIni := now;
  self.nHilosForzados := nHilosForzados;
  try
    if sala.inicializarOptimizacion_MULTIHILO_(nil, nil) = 0 then
      exit; // No hay variables de estado = no hay optimización

    if sala.globs.EstadoDeLaSala = CES_OPTIMIZANDO then
    begin
      // inicializa las variables y crea un robot por thread
      opt_inicializarVariablesMultiCore;
      sala.globs.procNot(sala.globs.procNot_opt_InicioOptimizacion);
      opt_crearTareas(0, sala.globs.CF.nEstrellasPorPuntoT - 1);
      sincrodata_mh.Paso := sala.globs.kPaso_Opt;
      sincrodata_mh.start_workers;
      kPasoIni := sala.globs.kPaso_Opt;

      {$IFDEF ESTABILIZAR_FRAMEINICIAL}
      if sala.estabilizarFrameInicial then
      begin
        writeln('Estabilizando frame inicial _MULTIHILO_.');
        RestarMinimoAlFrame(sala.globs.CF.constelacion_.fCosto[sala.globs.kPaso_Opt + 1]);
        cntIters := 0;
        stop := False;
        sala.globs.kCronica_ := 0;
        err_max_relativo := 0.05;

        while (not stop) and (cntIters < MAX_CANT_ITERS) do
        begin
          sincrodata_mh.dst_HacerTareas(True, sala.globs.kPaso_Opt, -1);
          err := VariacionEntreFrames(
            sala.globs.CF.constelacion_.fCosto[sala.globs.kPaso_Opt + 1],
            sala.globs.CF.constelacion_.fCosto[sala.globs.kPaso_Opt]);
     {$IFNDEF CLUSTER_MUTE}
          writeln(cntIters, ' : ', err: 12: 4);
     {$ENDIF}
          vswap(sala.globs.CF.constelacion_.fCosto[sala.globs.kPaso_Opt],
            sala.globs.CF.constelacion_.fCosto[sala.globs.kPaso_Opt + 1]);
          sala.globs.SwapAuxs;
          if err < err_max_relativo then
            stop := True;
          Inc(cntIters);
        end;
        if not stop then
          sala.globs.Alerta(sala.Nombre + ': Estabilizar frame inicial no convergio');
      end;
      {$ENDIF}

      while (sala.globs.kPaso_Opt > 0) and (not sala.globs.abortarSim) and
        (not sincrodata_mh.abortarSim) do
      begin
        if sala.globs.kPaso_Opt < kPasoIni then
        begin
          dtPaso := now;
          segundos_transcurridos := (dtPaso - dtIni) * 3600 * 24;
          segundos_restantes :=
            segundos_transcurridos / (kPasoIni - sala.globs.kPaso_Opt) *
            sala.globs.kPaso_Opt;
          sincrodata_mh.wrln('paso: ' + IntToStr(sala.globs.kPaso_Opt) +
            ', st: ' + IntToStr(trunc(segundos_transcurridos)) +
            ', sr: ' + IntToStr(trunc(segundos_restantes)));
        end;
        sincrodata_mh.dst_HacerTareas(True, sala.globs.kPaso_Opt, -1);
        opt_darPaso;
        sala.globs.procNot(sala.globs.procNot_opt_FinCalculosDeEtapa);
      end;
      // con esto le pido a todos los robots que se mueran
      sincrodata_mh.wrln('SalaMH.opt_OptimizarMulticore->Free_Workers BEGIN');
      sincrodata_mh.Free_workers;
      sincrodata_mh.wrln('SalaMH.opt_OptimizarMulticore->Free_Workers END');
      sala.FinOptimizacion;
    end;

    if not sala.globs.abortarSim then
    begin
      sala.globs.EstadoDeLaSala := CES_OPTIMIZACION_TERMINADA;
    end
    else
      sala.globs.EstadoDeLaSala := CES_OPTIMIZACION_ABORTADA;

    liberarVariablesMultiCore;

  except
    sala.globs.EstadoDeLaSala := CES_OPTIMIZACION_ABORTADA;
    sala.globs.abortarSim := True;
    if sincrodata_mh <> nil then
      liberarVariablesMultiCore;
    raise;
  end;
end;

function TGestorSalaMH.sim_SimularMultiCore(nHilosForzados: integer): NReal;
var
  aRobotSim: TRobotSimRangoCronicas;
  flg_OK: boolean;
  k: integer;

begin
  self.nHilosForzados := nHilosForzados;

  // inicializa las variables y crea un robot por thread
  sim_inicializarVariablesMultiCore(sala.globs.NCronicasSim_global_,
    sala.globs.NEstadosIniciales_global);


  sala.globs.procNot(sala.globs.procNot_InicioSimulacion);

  sim_crearTareas(1, sala.globs.NCronicasSim_global_,
    1,sala.globs.NEstadosIniciales_global);

  sincrodata_mh.Paso := 1;

  //writeln('Creando acumuladores');
  sala.Acumuladores_ := TAcumuladores_sim.Create(sala.globs, sala.globs.NTrayectorias_global);

  // writeln('... llamo start_workers ...');
  sincrodata_mh.start_workers;

  // writeln('dst_HacerTareas(True, 1, -1)');
  sincrodata_mh.dst_HacerTareas(True, 1, -1);


  flg_OK := True;
  for k := 0 to high(sincrodata_mh.robots) do
  begin
    aRobotSim := sincrodata_mh.robots[k] as TRobotSimRangoCronicas;
    if aRobotSim.GetEstadoSala <> CES_SIMULACION_TERMINADA then
    begin
      flg_OK := False;
      break;
    end;
  end;

  if flg_OK then
    sala.globs.EstadoDeLaSala := CES_SIMULACION_TERMINADA
  else
    sala.globs.EstadoDeLaSala := CES_SIMULACION_ABORTADA;

  // con esto le pido a todos los robots que se mueran
  {$IFDEF VERBOSO}
  writeln('Free_workers');
  {$ENDIF}

  sincrodata_mh.Free_workers;

  //  writeln('PrinArchi');
  Result := Sala.Acumuladores_.PrinArchi(sala.Archi_SimCosto);
  Sala.Acumuladores_.GetResumen(sala.ve_CF, sala.VaR05_CF, sala.CVaR05_CF);

  //  writeln('LiberarVariablesMulticore');
  liberarVariablesMultiCore;
end;



end.
