/*
 * Copyright (C) 1996-2011 Daniel Waggoner
 *
 * 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/>.
 */

/*******************************************************************************/
/************************** Elliptical Test Routines ***************************/
/*******************************************************************************/
#include "dw_matrix_sort.h"
#include "dw_elliptical.h"
#include "dw_rand.h"
#include "dw_matrix_rand.h"
#include "dw_parse_cmd.h"
#include "dw_std.h"

#include <stdio.h>
#include <math.h>

static void TestSimulateEllipticalVariance(TElliptical *elliptical, int count, FILE *f_out);
static void TestSimulateEllipticalCummulativeDensity(TElliptical *elliptical, int count, FILE *f_out);

// Prints the true and estimated cummulative densities
static void TestSimulateEllipticalCummulativeDensity(TElliptical *elliptical, int count, FILE *f_out)
{
  TVector R;
  int d=200, i, j;
  PRECISION min, max, inc, x;

  if (!f_out)
    {
      printf("Null file pointer\n");
      dw_exit(0);
    }

  PrintEllipticalInfo(f_out,elliptical);

  if (elliptical->cummulative_radius)
    {
      R=CreateVector(count);
      for (i=0; i < count; i++)
	ElementV(R,i)=DrawElliptical((PRECISION*)NULL,elliptical); 
      SortVectorAscending(R,R);

      min=ElementV(R,0);
      max=ElementV(R,count-1);
      inc=(max-min)/(PRECISION)(d-1);

      for (x=min, i=j=0; i < d; x+=inc, i++)
	{
	  for ( ; j < count; j++)
	    if (ElementV(R,j) >= x) break;
	  fprintf(f_out,"%lf,%lf,%lf\n",x,(double)j/(double)count,CummulativeDensityElliptical_Radius(x,elliptical));
	}
    }

  fprintf(f_out,"\n\n");
}

// Prints the difference, in terms of norms, between estimated and
// actual variances and means.
static void TestSimulateEllipticalVariance(TElliptical *elliptical, int count, FILE *f_out)
{
  TVector mean, v;
  TMatrix variance, x;
  int i;

  if (!f_out)
    {
      printf("Null file pointer\n");
      dw_exit(0);
    }

  PrintEllipticalInfo(f_out,elliptical);

  // Create matrices and vectors
  InitializeVector(mean=CreateVector(elliptical->dim),0.0);
  InitializeMatrix(variance=CreateMatrix(elliptical->dim,elliptical->dim),0.0);
  v=CreateVector(elliptical->dim);
  x=CreateMatrix(elliptical->dim,elliptical->dim);

  // Simulate mean and variance
  for (i=0; i < count; i++)
    {
      DrawElliptical(pElementV(v),elliptical);
      AddVV(mean,mean,v);
      OuterProduct(x,v,v);
      AddMM(variance,variance,x);
    }
  ProductVS(mean,mean,1/(PRECISION)count);
  ProductMS(variance,variance,1/(PRECISION)count);
  OuterProduct(x,mean,mean);
  SubtractMM(variance,variance,x);

  // Difference in centers
  fprintf(f_out,"acutal mean\n");
  dw_PrintVector(f_out,elliptical->center,"%lf ");
  fprintf(f_out,"difference between estimated and actual mean divided by standard deviation\n");
  SubtractVV(v,elliptical->center,mean);
  for (i=0; i < elliptical->dim; i++)
    fprintf(f_out,"%8.5lf ",ElementV(v,i)/sqrt(ElementM(variance,i,i)/count));
  fprintf(f_out,"\n");

  // Difference in variances
  ProductMS(x,elliptical->scale,sqrt(elliptical->variance_scale));
  ProductTransposeMM(x,x,x);
  SubtractMM(x,x,variance);
  fprintf(f_out,"norm of the difference between estimated and actual variance: %lf\n",MatrixNorm(x));

  // Prints the variance matrices
  fprintf(f_out,"Actual Variance:\n");
  ProductMS(x,elliptical->scale,sqrt(elliptical->variance_scale));
  ProductTransposeMM(x,x,x);
  dw_PrintMatrix(f_out,x,"%lg ");
  fprintf(f_out,"difference between estimated and actual variance:\n");
  SubtractMM(x,x,variance);
  dw_PrintMatrix(f_out,x,"%lg ");

  fprintf(f_out,"\n\n");

  // Clean up
  FreeMatrix(variance);
  FreeVector(mean);
  FreeVector(v);
  FreeMatrix(x);
}

int main(int nargs, char **args)
{
  TMatrix base_scale, scale, variance;
  TVector center;
  PRECISION lower_bound, upper_bound, power, *table;
  int i, seed, dim, count, table_size, cummulative;
  TElliptical *elliptical;
  FILE *f_out;

  count=dw_ParseInteger_String(nargs,args,"ndraws",10000);
  dim=dw_ParseInteger_String(nargs,args,"dim",3);
  lower_bound=dw_ParseFloating_String(nargs,args,"lower_bound",2.0);
  if (lower_bound < 0.0) lower_bound=0.0;
  upper_bound=dw_ParseFloating_String(nargs,args,"upper_bound",8.0);
  if (lower_bound >= upper_bound) upper_bound=lower_bound+1.0;
  power=dw_ParseFloating_String(nargs,args,"power",5.0);
  table_size=dw_ParseInteger_String(nargs,args,"table_size",10);
  cummulative=(dw_FindArgument_String(nargs,args,"cummulative") == -1) ? 0 : 1;
  seed=dw_ParseInteger_String(nargs,args,"seed",0);

  f_out=cummulative ? fopen("cummulative.csv","wt") : stdout;

  dw_initialize_generator(seed);

  dw_NormalVector(center=CreateVector(dim));
  dw_NormalMatrix(base_scale=CreateMatrix(dim,dim));
  variance=CreateMatrix(dim,dim);
  scale=CreateMatrix(dim,dim);

  fprintf(f_out,"Mean and Variance test\n");
  fprintf(f_out,"Count = %d\n",count);

  // Gaussian family
  variance=ProductTransposeMM((TMatrix)NULL,base_scale,base_scale);
  ProductMS(variance,variance,1.0/VarianceScale_TruncatedGaussian(dim,lower_bound,upper_bound));
  elliptical=CreateElliptical_TruncatedGaussian(dim,center,variance,lower_bound,upper_bound);
  if (cummulative)
    TestSimulateEllipticalCummulativeDensity(elliptical,count,f_out);
  else
    TestSimulateEllipticalVariance(elliptical,count,f_out);
  FreeElliptical(elliptical);

  variance=ProductTransposeMM((TMatrix)NULL,base_scale,base_scale);
  elliptical=CreateElliptical_Gaussian(dim,center,variance);
  if (cummulative)
    TestSimulateEllipticalCummulativeDensity(elliptical,count,f_out);
  else
    TestSimulateEllipticalVariance(elliptical,count,f_out);
  FreeElliptical(elliptical);
 
  // Truncated Power family 
  ProductMS(scale,base_scale,1.0/sqrt(VarianceScale_TruncatedPower(dim,lower_bound,upper_bound,power)));
  elliptical=CreateElliptical_TruncatedPower(dim,center,scale,lower_bound,upper_bound,power);
  if (cummulative)
    TestSimulateEllipticalCummulativeDensity(elliptical,count,f_out);
  else
    TestSimulateEllipticalVariance(elliptical,count,f_out);
  FreeElliptical(elliptical);

  ProductMS(scale,base_scale,1.0/sqrt(VarianceScale_TruncatedPower(dim,lower_bound,upper_bound,dim)));
  elliptical=CreateElliptical_Uniform(dim,center,scale,lower_bound,upper_bound);
  if (cummulative)
    TestSimulateEllipticalCummulativeDensity(elliptical,count,f_out);
  else
    TestSimulateEllipticalVariance(elliptical,count,f_out);
  FreeElliptical(elliptical);

  ProductMS(scale,base_scale,1.0/sqrt(VarianceScale_TruncatedPower(dim,0.0,upper_bound,power)));
  elliptical=CreateElliptical_Power(dim,center,scale,upper_bound,power);
  if (cummulative)
    TestSimulateEllipticalCummulativeDensity(elliptical,count,f_out);
  else
    TestSimulateEllipticalVariance(elliptical,count,f_out);
  FreeElliptical(elliptical);

  // Step
  table=(PRECISION*)dw_malloc((table_size+1)*sizeof(PRECISION));
  table[0]=lower_bound;
  for (i=1; i < table_size; i++)
    table[i]=table[i-1]+dw_uniform_rnd()*(upper_bound-table[i-1]);
  table[table_size]=upper_bound;
  ProductMS(scale,base_scale,1.0/sqrt(VarianceScale_Step(dim,table,table_size)));
  elliptical=CreateElliptical_Step(dim,center,scale,table,table_size);
  dw_free(table);
  if (cummulative)
    TestSimulateEllipticalCummulativeDensity(elliptical,count,f_out);
  else
    TestSimulateEllipticalVariance(elliptical,count,f_out);
  FreeElliptical(elliptical);

  // Clean up
  FreeMatrix(variance);
  FreeVector(center);
  FreeMatrix(scale);
  FreeMatrix(base_scale);

  return 0;
}
/*******************************************************************************/
/*******************************************************************************/
/*******************************************************************************/
