/*
 * Copyright (C) 1996-2011 Daniel Waggoner and Tao Zha
 *
 * This free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * It is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * If you did not received a copy of the GNU General Public License
 * with this software, see <http://www.gnu.org/licenses/>.
 */

#include "switch.h"
#include "switchio.h"
#include "switch_opt.h"
#include "VARbase.h"
#include "VARio.h"
#include "dw_error.h"
#include "dw_ascii.h"
#include "dw_parse_cmd.h"
#include "dw_rand.h"
#include "dw_sbvar_command_line.h"
#include "dw_std.h"
#include "sbvar_estimate.h"

#include "dw_csminwel.h"

#include <time.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>

/*******************************************************************************/
/*******************************************************************************/
/*******************************************************************************//*
   Obtains estimate info from command line.  

   Additional command line parameters

    BLOCKWISE ESTIMATION CONTROL    
     -cb <real>
        convergence criterion starting value (default value = 1.0e-03)
     -ce <real>
        convergence criterion ending value (default value = 1.03-06)
     -ci <real>
        convergence criterion increment value (default value = 0.1)
     -ib <integer>
        maximum interations starting value (default value = 50)
     -ii <real>
        maximum interations increment value (default value = 2.0)
     -mb <integer>
        maximum block iterations (default value = 100)

    REPEATING ESTIMATION TO ENSURE LOCAL CONVERGENCE
     -repeat_max <integer>
        maximum number of times to repeat csminwel runs.  (default value = 10)
     -repeat_tol_obj <real>
        convergence criterion for objective function (default value = 0.1)
     -repeat_tol_parms <real>
        convergence criterion for parameter values (default value = 0.1)

    PERTURBATION TECHNIQUES TO ENSURE GLOBAL CONVERGENCE
     -random <integer>
        number of large perturbations to make at each stage of the estimation 
        process. (default value = 5) 
     -random_small <integer>
        number of small perturbations to make after the large perturbations have
        stopped inproving. (default value = 5)
     -random_small_ndraws <integer>
        number of consecutive posterior draws to make when producing a small
        perturbation. (default value = 1)
     -random_max <integer>
        maximum number of stages allowed (default value = 20)
     -random_tol_obj <real>
        convergence criterion for objective function (default value = 0.1)
     -random_tol_parms <real>
        convergence criterion for parameter values (default value = 0.1)

*/
TEstimateInfo* SetupEstimateFromVARCommandLine(int nargs, char **args, TVARCommandLine *cmd)
{ 
  TEstimateInfo *info;

  if (!cmd) return (TEstimateInfo*)NULL;

  // Create info
  info=(TEstimateInfo*)dw_malloc(sizeof(TEstimateInfo));
  info->cmd=cmd;

  // Default values
  info->criterion_start=dw_ParseFloating_String(nargs,args,"cb",1.0e-3);
  info->criterion_end=dw_ParseFloating_String(nargs,args,"ce",1.0e-6);
  info->criterion_increment=dw_ParseFloating_String(nargs,args,"ci",0.1);
  info->max_iterations_start=dw_ParseInteger_String(nargs,args,"ib",50);
  info->max_iterations_increment=dw_ParseFloating_String(nargs,args,"ii",2.0);
  info->max_block_iterations=dw_ParseInteger_String(nargs,args,"mb",100);

  info->repeat_max=dw_ParseInteger_String(nargs,args,"repeat_max",10);
  info->repeat_tol_obj=dw_ParseFloating_String(nargs,args,"repeat_tol_obj",0.1);
  info->repeat_tol_parms=dw_ParseFloating_String(nargs,args,"repeat_tol_parms",0.1);

  info->random=dw_ParseInteger_String(nargs,args,"random",5);
  info->random_small=dw_ParseInteger_String(nargs,args,"random_small",5);
  info->random_small_ndraws=dw_ParseInteger_String(nargs,args,"random_small_ndraws",1);
  info->random_max=dw_ParseInteger_String(nargs,args,"random_max",20);
  info->random_tol_obj=dw_ParseFloating_String(nargs,args,"random_tol_obj",0.1);
  info->random_tol_parms=dw_ParseFloating_String(nargs,args,"random_tol_parms",0.1);

  // Output tag
  OutTag_VARCommandLine(info->cmd);

   // Output filenames
  info->csminwel_output_filename=CreateFilenameFromTag("%sest_csminwel_%s.out",info->cmd->out_tag,info->cmd->out_directory);
  info->intermediate_output_filename=CreateFilenameFromTag("%sest_intermediate_%s.out",info->cmd->out_tag,info->cmd->out_directory);
  if (info->cmd->out_filename)
    info->final_output_filename=CreateFilenameFromTag("%s%s",info->cmd->out_filename,info->cmd->out_directory);
  else
    info->final_output_filename=CreateFilenameFromTag("%sest_final_%s.out",info->cmd->out_tag,info->cmd->out_directory);

  return info;
}

TEstimateInfo* SetupEstimateFromCommandLine(int nargs, char **args)
{ 
  return SetupEstimateFromVARCommandLine(nargs,args,CreateTStateModel_VARCommandLine(nargs,args,(TVARCommandLine*)NULL));
}

void FreeEstimateInfo(TEstimateInfo *info)
{
  if (info)
    {
      if (info->csminwel_output_filename) dw_free(info->csminwel_output_filename);
      if (info->intermediate_output_filename) dw_free(info->intermediate_output_filename);
      if (info->final_output_filename) dw_free(info->final_output_filename);
      Free_VARCommandLine(info->cmd);
      dw_free(info);
    }
}
/*******************************************************************************/
/*******************************************************************************/
/*******************************************************************************/

void FindMode_VAR_csminwel(TEstimateInfo *estimate, FILE *f)
{
  TStateModel *model=estimate->cmd->model;
  int iteration, total_iteration, i, j, size_VAR, pos_VAR, size_Q, pos_Q;
  double objective, objective_last, likelihood, prior;
  int **block;
  FILE *f_out;
  char *header, *fmt="Iteration %d: ";

  // csminwel arguments 
  int itct, fcount, retcodeh, nit;
  double *x, fh, crit;
  TMatrix H;
  TVector g;

  f_out=f ? f : dw_CreateTextFile(estimate->intermediate_output_filename);

  //==== Allocate memory  ===
  size_VAR=NumberFreeParametersTheta(model);
  size_Q=NumberFreeParametersQ(model);
  pos_VAR=0;
  pos_Q=size_VAR;
  x=(double*)dw_malloc((size_VAR + size_Q)*sizeof(double));

  //=== Set starting value ===
  ConvertQToFreeParameters(model,x+pos_Q);
  ConvertThetaToFreeParameters(model,x+pos_VAR);

  //=== Set csminwel output file ===
  csminwel_SetPrintFile(estimate->csminwel_output_filename);

  //=== Print Initial Values ===
  fprintf(f_out,"\n//=== Initial Values ===//\n");
  fprintf(f_out,"Likelihood value:  %22.14le\n",objective=likelihood=LogLikelihood_StatesIntegratedOut(model));
  fprintf(f_out,"Prior value:  %22.14le\n",prior=LogPrior(model));
  fprintf(f_out,"Posterior value:  %22.14le\n\n",likelihood+prior);

  header="Initial: ";
  WriteBaseTransitionMatrices(f_out,(char*)NULL,header,model);
  Write_VAR_Parameters(f_out,(char*)NULL,header,model);
  fflush(f_out);

  //=== Create blocking structure ===
  block=dw_CreateRectangularArray_int(2,2);
  block[0][0]=size_VAR;         block[0][1]=pos_VAR;
  block[1][0]=size_Q;           block[1][1]=pos_Q;

  //=== Objective ===
  if (estimate->cmd->MLE)
    objective=likelihood;
  else
    objective=likelihood+prior;

  for (total_iteration=1, crit=estimate->criterion_start, nit=estimate->max_iterations_start; 
       crit >= estimate->criterion_end; 
       crit*=estimate->criterion_increment, nit*=(int)estimate->max_iterations_increment)
    {
      for (iteration=1; iteration <= estimate->max_block_iterations; total_iteration++, iteration++)
	      {
      	  objective_last=objective;

      	  fprintf(f_out,"\n\n//=== Iteration %d ===//\n",total_iteration);
      	  fprintf(f_out,"Criterion/Max Iteration:  %le  %d\n",crit,nit);
      	  fprintf(f_out,"Previous likelihood value:  %22.14le\n",likelihood);
      	  fprintf(f_out,"Previous prior value:  %22.14le\n",prior);
      	  fprintf(f_out,"Previous posterior value:  %22.14le\n\n",prior+likelihood);
      	  fflush(f_out);

      	  for (i=0; i < dw_DimA(block); i++)
      	    if (block[i][0] > 0)
      	      {
      		g=CreateVector(block[i][0]);
      		H=IdentityMatrix((TMatrix)NULL,block[i][0]);
      		ProductMS(H,H,INI_H_CSMINWEL);

      		SetupObjectiveFunction(model,x+block[i][1],x+pos_Q,x+pos_VAR);

      		if (estimate->cmd->MLE)
      		  csminwel(MLEObjectiveFunction_csminwel,x+block[i][1],block[i][0],pElementM(H),pElementV(g),NULL,
      			   &fh,crit,&itct,nit,&fcount,&retcodeh,NULL,NULL);
      		else
      		  csminwel(PosteriorObjectiveFunction_csminwel,x+block[i][1],block[i][0],pElementM(H),pElementV(g),NULL,
      			   &fh,crit,&itct,nit,&fcount,&retcodeh,NULL,NULL);

      		ConvertFreeParametersToQ(model,x+pos_Q);
      		ConvertFreeParametersToTheta(model,x+pos_VAR);

      		FreeMatrix(H);
      		FreeVector(g);

      		fprintf(f_out,"Likelihood value after pass %d:  %22.14le\n",i,likelihood=LogLikelihood_StatesIntegratedOut(model));
      		fprintf(f_out,"Prior value after pass %d:  %22.14le\n",i,prior=LogPrior(model));
      		fprintf(f_out,"Posterior value after pass %d:  %22.14le\n",i,likelihood+prior);
      		fprintf(f_out,"Csminwel return code: %d\n\n",retcodeh);
      		fflush(f_out);
      	      }

      	  for (j=10, i=1; total_iteration >= j; j*=10, i++);
      	  sprintf(header=(char*)dw_malloc(strlen(fmt) + i - 1),fmt,total_iteration);
      	  WriteBaseTransitionMatrices(f_out,(char*)NULL,header,model);
      	  Write_VAR_Parameters(f_out,(char*)NULL,header,model);
      	  dw_free(header);
      	  fflush(f_out);

      	  if (estimate->cmd->MLE)
      	    objective=likelihood;
      	  else
      	    objective=likelihood+prior;

      	  if (fabs(objective - objective_last) <= crit) break;
      	}

      objective_last=objective;

      fprintf(f_out,"\n\n//=== Iteration %d ===//\n",++total_iteration);
      fprintf(f_out,"Criterion/Max Iteration:  %le  %d\n",crit,nit);
      fprintf(f_out,"Previous likelihood value:  %22.14le\n",likelihood);
      fprintf(f_out,"Previous prior value:  %22.14le\n",prior);
      fprintf(f_out,"Previous posterior value:  %22.14le\n\n",prior+likelihood);
      fflush(f_out);

      g=CreateVector(pos_Q+pos_VAR);
      H=IdentityMatrix((TMatrix)NULL,pos_Q+pos_VAR);
      ProductMS(H,H,INI_H_CSMINWEL);

      SetupObjectiveFunction(model,x,x+pos_Q,x+pos_VAR);

      if (estimate->cmd->MLE)
      	csminwel(MLEObjectiveFunction_csminwel,x,pos_Q+pos_VAR,pElementM(H),pElementV(g),NULL,
      		      &fh,crit,&itct,nit,&fcount,&retcodeh,NULL,NULL);
      else
      	csminwel(PosteriorObjectiveFunction_csminwel,x,pos_Q+pos_VAR,pElementM(H),pElementV(g),NULL,
      		      &fh,crit,&itct,nit,&fcount,&retcodeh,NULL,NULL);	

      ConvertFreeParametersToQ(model,x+pos_Q);
      ConvertFreeParametersToTheta(model,x+pos_VAR);

      FreeMatrix(H);
      FreeVector(g);

      fprintf(f_out,"Likelihood value:  %22.14le\n",likelihood=LogLikelihood_StatesIntegratedOut(model));
      fprintf(f_out,"Prior value:  %22.14le\n",prior=LogPrior(model));
      fprintf(f_out,"Posterior value:  %22.14le\n",likelihood+prior);
      fprintf(f_out,"Csminwel return code: %d\n\n",retcodeh);
      fflush(f_out);

      for (j=10, i=1; total_iteration >= j; j*=10, i++);
      sprintf(header=(char*)dw_malloc(strlen(fmt) + i - 1),fmt,total_iteration);
      WriteBaseTransitionMatrices(f_out,(char*)NULL,header,model);
      Write_VAR_Parameters(f_out,(char*)NULL,header,model);
      dw_free(header);
      fflush(f_out);

      if (estimate->cmd->MLE)
      	objective=likelihood;
      else
      	objective=likelihood+prior;
    }

  //=== Free memory ===
  dw_free(x);
  dw_FreeArray(block);

  //=== Close File ===
  if (!f) fclose(f_out);
}

/*******************************************************************************/
/******************************* Old Estimation ********************************/
/*******************************************************************************/
/*
   There was a bug in the original coded that could cause a perturbation in the 
   initial values.  If was found that this perturbation sometimes inproved the
   performance of the estimation procedure.  We include this technique so the 
   results of original code can reproduced.  A more systematic approach to
   perturbation is now used.
*/
static void OriginalPerturbation(TEstimateInfo *info, FILE *f_intermediate)
{
  ReadBaseTransitionMatrices((FILE*)NULL,info->cmd->parameters_filename_actual,info->cmd->parameters_header_actual,info->cmd->model);
  Read_VAR_Parameters_Original((FILE*)NULL,info->cmd->parameters_filename_actual,info->cmd->parameters_header_actual,info->cmd->model);

  FindMode_VAR_csminwel(info,f_intermediate);
}

/*******************************************************************************/
/*******************************************************************************/
/*******************************************************************************/

int RepeatEstimation(TEstimateInfo *info, FILE *f_intermediate, FILE *f_final)
{
  TStateModel *model=info->cmd->model;
  TVector old_parameters, parameters;
  PRECISION old_obj, obj, norm;
  time_t begin_time;
  int i;

  old_parameters=CreateVector(NumberFreeParametersTheta(model)+NumberFreeParametersQ(model));
  parameters=CreateVector(NumberFreeParametersTheta(model)+NumberFreeParametersQ(model));

  ConvertThetaToFreeParameters(model,pElementV(old_parameters));
  ConvertQToFreeParameters(model,pElementV(old_parameters)+NumberFreeParametersTheta(model));
  old_obj=info->cmd->MLE ? LogLikelihood_StatesIntegratedOut(model) : LogPosterior_StatesIntegratedOut(model);

  begin_time=time((time_t*)NULL);
  if (f_final)
    {
      fprintf(f_final,"Time stamp:  %s",ctime(&begin_time));
      fprintf(f_final,"Objective function (initial): %lg\n",old_obj);
    }
  for (i=0; i < info->repeat_max; i++)
    {
      FindMode_VAR_csminwel(info,f_intermediate);
      ConvertThetaToFreeParameters(model,pElementV(parameters));
      ConvertQToFreeParameters(model,pElementV(parameters)+NumberFreeParametersTheta(model));
      obj=info->cmd->MLE ? LogLikelihood_StatesIntegratedOut(model) : LogPosterior_StatesIntegratedOut(model);
      norm=Norm(SubtractVV(old_parameters,old_parameters,parameters));
      if (f_final) fprintf(f_final,"Objective function (run %d): %lg  (Norm parameter change: %le  Objective function change: %le)\n",i+1,obj,norm,obj-old_obj);
      if ((norm <= info->repeat_tol_parms) && (fabs(obj-old_obj) <= info->repeat_tol_obj)) break;
      old_obj=obj;
      EquateVector(old_parameters,parameters);
    }

  FreeVector(old_parameters);
  FreeVector(parameters);

  return i;
}

static void GetSimulation(TVector *simulation, TStateModel *model)
{
  int tuning=30000, burnin=10000, thin=80, ndraws, count, i, begin_time, end_time, check, period=1000;
  TVector parameters;
  T_VAR_Parameters *p=(T_VAR_Parameters*)(model->theta);

  // Allocate parameters workspace
  parameters=CreateVector(NumberFreeParametersTheta(model)+NumberFreeParametersQ(model));

  // Save initial parameters
  ConvertThetaToFreeParameters(model,pElementV(parameters));
  ConvertQToFreeParameters(model,pElementV(parameters)+NumberFreeParametersTheta(model));

  // Burn-in period with calibration of jumping parameters
  printf("Calibrating jumping parameters - %d draws\n",tuning);
  begin_time=(int)time((time_t*)NULL);
  AdaptiveMetropolisScale(model,tuning,1000,1,(FILE*)NULL);      // tuning iterations - 1000 iterations before updating - verbose
  end_time=(int)time((time_t*)NULL);
  printf("Elapsed Time: %d seconds\n",end_time - begin_time);

  // Reset initial parameters
  ConvertFreeParametersToTheta(model,pElementV(parameters));
  ConvertFreeParametersToQ(model,pElementV(parameters)+NumberFreeParametersTheta(model));

  // Burn-in period
  printf("Burn-in period - %d draws\n",burnin);
  for (check=period, count=1; count <= burnin; count++)
    {
      DrawAll(model);

      if (count == check)
	{
	  check+=period;
	  printf("%d iterations completed out of %d\n",count,burnin);
	  end_time=(int)time((time_t*)NULL);
	  printf("Elapsed Time: %d seconds\n",end_time - begin_time);
	}
    }
  end_time=(int)time((time_t*)NULL);
  printf("Elapsed Time: %d seconds\n",end_time - begin_time);
  ResetMetropolisInformation(p);
 
  // Simulation
  ndraws=dw_DimA(simulation);
  printf("Simulating - %d draws\n",ndraws);
  for (check=period, count=0; count < ndraws; count++)
    {
      for (i=thin; i > 0; i--) DrawAll(model);

      ConvertThetaToFreeParameters(model,pElementV(simulation[count]));
      ConvertQToFreeParameters(model,pElementV(simulation[count])+NumberFreeParametersTheta(model));;

      if (count == check)
	{
	  check+=period;
	  printf("%d * %d iterations completed out of %d * %d\n",count,thin,ndraws,thin);
	  end_time=(int)time((time_t*)NULL);
	  printf("Elapsed Time: %d seconds\n",end_time - begin_time);
	}
    }
  end_time=(int)time((time_t*)NULL);
  printf("Total Elapsed Time: %d seconds\n",end_time - begin_time);
}


/*
   Additional command line parameters

     See comments before the SetupEstimateFromVARCommandLine() routine.
*/
void dw_sbvar_estimate_command_line(int nargs, char **args)
{
  TStateModel *model;
  TEstimateInfo *info;
  FILE *f_out, *f_intermediate, *f_final, *f_free;
  char *filename;
  time_t begin_time, end_time;
  int t, i, j, k, nsim=500, nq, ntheta, nparms;
  PRECISION old_obj, max_obj, obj, norm;
  TVector y, old_parameters, max_parameters, *simulation;

  /* Get estimation information */
  if (!(info=SetupEstimateFromCommandLine(nargs,args)) || !info->cmd)
    dw_Error(MEM_ERR);
  else
    if (!info->cmd->model)
      dw_UserError("Unable to setup SBVAR from command line parameters");
    else
      if (!info->cmd->parameters_filename_actual)
	dw_UserError("Unable to initialize parameters from command line parameters");
      else
	{
	  model=info->cmd->model;
	  nparms=(ntheta=NumberFreeParametersTheta(model))+(nq=NumberFreeParametersQ(model));

	  // Setup no normalization
	  Setup_No_Normalization_VAR((T_VAR_Parameters*)model->theta);

	  /* Output header */
	  if (!info->cmd->out_header)
	    info->cmd->out_header=dw_DuplicateString(info->cmd->MLE ? "MLE: " : "Posterior mode: ");

	  /* Open temporary final output file */
	  if (f_intermediate=dw_CreateTextFile(info->intermediate_output_filename))
	    {
	      sprintf(filename=(char*)dw_malloc(5+strlen(info->final_output_filename)),"%s.tmp",info->final_output_filename);
	      if (f_final=dw_CreateTextFile(filename))
		{
		  Write_VAR_Specification(f_final,(char*)NULL,model);
		  fprintf(f_final,"Specification file: %s\n",info->cmd->specification_filename_actual);
		  fprintf(f_final,"Initialization file: %s\n",info->cmd->parameters_filename_actual);
		  fprintf(f_final,"Initialization header: \"%s\"\n",info->cmd->parameters_header_actual);

		  fprintf(f_final,"Number free parameters in transition matrix: %d\n",NumberFreeParametersQ(model));
		  fprintf(f_final,"Number free parameters in theta: %d\n",NumberFreeParametersTheta(model));

		  fprintf(f_final,"Initial likelihood Value: %g\n",LogLikelihood_StatesIntegratedOut(model));
		  fprintf(f_final,"Initial prior Value: %g\n",LogPrior(model));
		  fprintf(f_final,"Initial posterior Value: %g\n",LogPosterior_StatesIntegratedOut(model));

		  begin_time=time((time_t*)NULL);

		  if (dw_FindArgument_String(nargs,args,"OriginalPerturbation") != -1)
		    {
		      OriginalPerturbation(info,f_intermediate);
		    }
		  else
		    {
		      /* First estimation */
		      RepeatEstimation(info,f_intermediate,f_final);

		      /* Random perturbations */
		      if (info->random_max > 0)
			{
			  simulation=dw_CreateArray_vector(nsim);
			  for (i=nsim-1; i >= 0; i--) simulation[i]=CreateVector(nparms);
			  max_parameters=CreateVector(nparms);
			  old_parameters=CreateVector(nparms);

			  ConvertThetaToFreeParameters(model,pElementV(max_parameters));
			  ConvertQToFreeParameters(model,pElementV(max_parameters)+ntheta);
			  max_obj=info->cmd->MLE ? LogLikelihood_StatesIntegratedOut(model) : LogPosterior_StatesIntegratedOut(model);

			  fprintf(f_final,"//== End initial run: maximum objective = %le\n",max_obj);

			  for (i=0; i < info->random_max; i++)
			    {
			      EquateVector(old_parameters,max_parameters);
			      old_obj=max_obj;

			      /* Simulate */
			      GetSimulation(simulation,model);

			      for (j=0; j < info->random; j++)
				{
				  /* Select draw */
				  k=(int)floor(nsim*dw_uniform_rnd());
				  ConvertFreeParametersToTheta(model,pElementV(simulation[k]));
				  ConvertFreeParametersToQ(model,pElementV(simulation[k])+ntheta);

				  /* Estimate */
				  RepeatEstimation(info,f_intermediate,f_final);

				  /* Uphill? */
				  obj=info->cmd->MLE ? LogLikelihood_StatesIntegratedOut(model) : LogPosterior_StatesIntegratedOut(model);
				  if (obj > max_obj)
				    {
				      ConvertThetaToFreeParameters(model,pElementV(max_parameters));
				      ConvertQToFreeParameters(model,pElementV(max_parameters)+ntheta);
				      max_obj=obj;
				    }
				}

			      /* Reset parameters */
			      ConvertFreeParametersToTheta(model,pElementV(max_parameters));
			      ConvertFreeParametersToQ(model,pElementV(max_parameters)+ntheta);

			      /* Improved? */
			      norm=Norm(SubtractVV(old_parameters,old_parameters,max_parameters));
			      fprintf(f_final,"//== End perturbation run %d: maximum objective = %le  (norm parameter change: %le  objective function change: %le)\n",i+1,
				      max_obj,norm,max_obj-old_obj);
			      fflush(f_final);
			      if ((norm <= info->random_tol_parms) && (fabs(max_obj-old_obj) <= info->random_tol_obj))
				{
				  EquateVector(old_parameters,max_parameters);
				  old_obj=max_obj;

				  /* Local perturbation */
				  for (j=0; j < info->random_small; j++)
				    {
				      /* Select draw */
				      for (k=info->random_small_ndraws; k > 0; k--) DrawAll(model);

				      /* Estimate */
				      RepeatEstimation(info,f_intermediate,f_final);

				      /* Uphill? */
				      obj=info->cmd->MLE ? LogLikelihood_StatesIntegratedOut(model) : LogPosterior_StatesIntegratedOut(model);
				      if (obj > max_obj)
					{
					  ConvertThetaToFreeParameters(model,pElementV(max_parameters));
					  ConvertQToFreeParameters(model,pElementV(max_parameters)+ntheta);
					  max_obj=obj;
					}

				      /* Reset Parameters */
				      ConvertFreeParametersToTheta(model,pElementV(max_parameters));
				      ConvertFreeParametersToQ(model,pElementV(max_parameters)+ntheta);
				    }
			      
				  /* Improved? */
				  norm=Norm(SubtractVV(old_parameters,old_parameters,max_parameters));
				  fprintf(f_final,"//== End local perturbation run: maximum objective = %le  (norm parameter change: %le  objective function change: %le)\n",max_obj,norm,max_obj-old_obj);
				  fflush(f_final);
				  if ((norm <= info->random_tol_parms) && (fabs(max_obj-old_obj) <= info->random_tol_obj)) break;
				}
			    }

			  FreeVector(old_parameters);
			  FreeVector(max_parameters);
			  dw_FreeArray(simulation);
			}
		    }

		  end_time=time((time_t*)NULL);

		  /* Normalize */
		  Setup_Default_Recursive_Normalization_VAR((T_VAR_Parameters*)model->theta);
		  Normalize_VAR(model,(PRECISION*)NULL);

		  /* Write final output */
		  fprintf(f_final,"Time stamp:  %s",ctime(&end_time));
		  fprintf(f_final,"Elapsed time: %d seconds\n",(int)end_time-(int)begin_time);

		  fprintf(f_final,"Final likelihood Value: %g\n",LogLikelihood_StatesIntegratedOut(model));
		  fprintf(f_final,"Final prior Value: %g\n",LogPrior(model));
		  fprintf(f_final,"Final posterior Value: %g\n\n",LogPosterior_StatesIntegratedOut(model));

		  fprintf(f_final,"//== %sFree parameters ==//\n",info->cmd->out_header);
		  WriteFreeParameters(f_final,(char*)NULL,model);
		  fprintf(f_final,"\n");

		  WriteBaseTransitionMatrices(f_final,(char*)NULL,info->cmd->out_header,model);
		  Write_VAR_Parameters(f_final,(char*)NULL,info->cmd->out_header,model);
		  fclose(f_final);

		  /* Rename temp file */
		  rename(filename,info->final_output_filename);
		  dw_free(filename);
		  
		  /* Write Free Parameter File */
		  filename=CreateFilenameFromTag("%sest_free_%s.out",info->cmd->out_tag,info->cmd->out_directory);
		  if (f_free = fopen(filename, "wt"))
		    {
		      max_parameters=CreateVector(nparms);
		      fprintf(f_free,"%le %le ",LogPosterior_StatesIntegratedOut(model),LogLikelihood_StatesIntegratedOut(model));
		      ConvertThetaToFreeParameters(model,pElementV(max_parameters));
		      ConvertQToFreeParameters(model,pElementV(max_parameters)+NumberFreeParametersTheta(model));
		      dw_PrintVector(f_free,max_parameters,"%le ");
		      fclose(f_free);
		      FreeVector(max_parameters);
		    }
		  dw_free(filename);

		  /* Write flat file */
		  filename=CreateFilenameFromTag("%sest_flat_header_%s.out",info->cmd->out_tag,info->cmd->out_directory);
		  if (f_out=fopen(filename,"wt"))
		    {     
		      WriteBaseTransitionMatricesFlat_Headers(f_out,model);
		      Write_VAR_ParametersFlat_Headers(f_out,model);
		      fprintf(f_out,"\n");
		      fclose(f_out);
		    }
		  dw_free(filename);
		  filename=CreateFilenameFromTag("%sest_flat_%s.out",info->cmd->out_tag,info->cmd->out_directory);
		  if (f_out=fopen(filename,"wt"))
		    {     
		      WriteBaseTransitionMatricesFlat(f_out,(char*)NULL,model,"%lf ");
		      if (dw_FindArgument_String(nargs,args,"nd1") >= 0)
			Write_VAR_ParametersFlat_A0_Diagonal_One(f_out,model,"%lf ");
		      else
			Write_VAR_ParametersFlat(f_out,model,"%lf ");
		      fprintf(f_out,"\n");
		      fclose(f_out);
		    }
		  dw_free(filename);
		  /**/

#if defined(MATLAB_MEX_FILE) || defined(OCTAVE_MEX_FILE)
                  /* Write Output to Matlab/Octave .mat file*/
                  Write_VAR_Parameters_Mat(model, info->cmd->out_tag);
#endif

		  /* Write aux output */
		  filename=CreateFilenameFromTag("%sest_aux_%s.out",info->cmd->out_tag,info->cmd->out_directory);
		  if (f_out=fopen(filename,"wt"))
		    {
		      fprintf(f_out,"""ln(P(y[t]|Y[t-1],Z[t],theta,Q))"",""E[y[t]|Y[t-1],Z[t],theta,Q]""\n");

		      y=CreateVector(((T_VAR_Parameters*)(model->theta))->nvars);
		      for (t=1; t <= model->nobs; t++)
			{
			  fprintf(f_out,"%le,",LogConditionalLikelihood_StatesIntegratedOut(t,model));
			  if (ExpectationSingleStep_StatesIntegratedOut(y,t,model))
			    dw_PrintVector(f_out,y,"%le,");
			  else
			    fprintf(f_out,"\n");
			}

		      FreeVector(y);
		      fclose(f_out);  
		    }
		  dw_free(filename);
		  /**/
		}
	      else
		dw_free(filename);
	      fclose(f_intermediate);
	    }
	}

  // Free memory
  FreeEstimateInfo(info);
}
