/*****************************************************************************
 *                                                                           *
 *          UNURAN -- Universal Non-Uniform Random number generator          *
 *                                                                           *
 *****************************************************************************
 *                                                                           *
 *   FILE:      testroutines.c                                               *
 *                                                                           *
 *   Common test routines                                                    *
 *                                                                           *
 *****************************************************************************
 *                                                                           *
 *   Copyright (c) 2000-2011 Wolfgang Hoermann and Josef Leydold             *
 *   Department of Statistics and Mathematics, WU Wien, Austria              *
 *                                                                           *
 *   This program is 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 2 of the License, or       *
 *   (at your option) any later version.                                     *
 *                                                                           *
 *   This program 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.                            *
 *                                                                           *
 *   You should have received a copy of the GNU General Public License       *
 *   along with this program; if not, write to the                           *
 *   Free Software Foundation, Inc.,                                         *
 *   59 Temple Place, Suite 330, Boston, MA 02111-1307, USA                  *
 *                                                                           *
 *****************************************************************************/

/*---------------------------------------------------------------------------*/

#include "testunuran.h"
#include <signal.h>

#ifdef HAVE_UNISTD_H
#  include <unistd.h>
#endif

/*---------------------------------------------------------------------------*/
/* Compiler switches                                                         */

/* whether to test U-errors for inversion method in tails more accurately */
#define TEST_INVERROR_TAILS (FALSE)

/*---------------------------------------------------------------------------*/
/* global switches                                                           */

static int stopwatch = FALSE;   /* whether to use a stop watch for checks    */ 
static TIMER vw;                /* timer for particular tests                */

/*---------------------------------------------------------------------------*/

#if defined(HAVE_ALARM) && defined(HAVE_SIGNAL)

/* time limit before SIGALRM is sent to process */
int time_limit = 0u;

/* handle SIGALRM signals */
static void catch_alarm (int sig);

/* test log file */
static FILE *TESTLOG = NULL;

#endif

/*---------------------------------------------------------------------------*/

/* draw sample */
static int draw_sample_and_discard ( FILE *LOG, int line, UNUR_GEN *gen, int sample_size );

/* compare two doubles */
static int compare_doubles ( double x1, double x2 );

/* compare double sequences generated by generator */
static int compare_double_sequence_gen_start ( FILE *LOG, int line, UNUR_GEN *gen, int sample_size );
static int compare_double_sequence_gen       ( FILE *LOG, int line, UNUR_GEN *gen, int sample_size );

/* compare int sequences generated by generator    */
static int compare_int_sequence_gen_start    ( FILE *LOG, int line, UNUR_GEN *gen, int sample_size );
static int compare_int_sequence_gen          ( FILE *LOG, int line, UNUR_GEN *gen, int sample_size );

/* compare sequences of double vectors generated by generator */
static int compare_cvec_sequence_gen_start   ( FILE *LOG, int line, UNUR_GEN *gen, int sample_size );
static int compare_cvec_sequence_gen         ( FILE *LOG, int line, UNUR_GEN *gen, int sample_size );

/* compare sequences of double matrices generated by generator */
static int compare_matr_sequence_gen_start   ( FILE *LOG, int line, UNUR_GEN *gen, int sample_size );
static int compare_matr_sequence_gen         ( FILE *LOG, int line, UNUR_GEN *gen, int sample_size );

/*---------------------------------------------------------------------------*/
/* check whether unur_urng_reset() works                                     */

static int 
cannot_compare_sequence ( FILE *LOG )
{
  fprintf (LOG,"\nURNG cannot be reset. Cannot compare sequences. (Skip)\n");
  printf ("URNG cannot be reset. Cannot compare sequences. (Skip)\n");

  return UNUR_SUCCESS; /* indicate as "not failed" for practical reasons */
} /* end of cannot_compare_sequence() */

/*---------------------------------------------------------------------------*/
/* stop watch                                                                */

void stopwatch_start(TIMER *t)
/* start stop watch */
{
  t->stop = stopwatch_get_time(t->tv);
  t->interim = t->stop;
  t->start = t->stop;
}

double stopwatch_lap(TIMER *t)
/* return elapsed time (in ms) since last call to stopwatch_start(),
   stopwatch_stop(), or stopwatch_lap() */
{
  double etime;

  t->stop = stopwatch_get_time(t->tv);
  etime = t->stop - t->interim;

  t->interim = t->stop;

  return etime;
}

double stopwatch_stop(TIMER *t)
/* return elapsed time (in ms) since last call to stopwatch_start() 
   or stopwatch_stop() */
{
  double etime;

  t->stop = stopwatch_get_time(t->tv);
  etime = t->stop - t->start;

  t->interim = t->stop;
  t->start = t->stop;

  return etime;
}

void stopwatch_init(void)
/* initialize watch */
{
  /* detect whether stopwatch should be used */
  stopwatch = (getenv("UNURANSTOPWATCH")==NULL) ? FALSE : TRUE;
  stopwatch_start(&vw);
}

void stopwatch_print( FILE *LOG, const char *format, double etime )
/* print time depending whether watch is enabled or not */
{
  if (stopwatch) 
    fprintf(LOG,format,etime);
}

/*---------------------------------------------------------------------------*/
/* exit with FAIL when run time exceeds limit                                */
 
#if defined(HAVE_ALARM) && defined(HAVE_SIGNAL)

void catch_alarm (int sig ATTRIBUTE__UNUSED)
{
  fprintf(stdout," aborted! time limit of %d seconds exceeded ...\n",time_limit);
  fprintf(TESTLOG,"\n\n=== ABORTED! ===\ntime limit of %d seconds exceeded ...\n\n",time_limit);
  exit (EXIT_FAILURE);
}

void set_alarm(FILE *LOG)
{
  char *read_timer;

  /* file handle for test log file */
  TESTLOG = LOG;

  /* read time limit from environment */
  read_timer = getenv("UNURANTIMER");
  time_limit = (read_timer!=NULL) ? atoi(read_timer) : 0;

  /* alarm is only set when environment variable is defined */
  if (time_limit<=0) return;
  
  /* Establish a handler for SIGALRM signals. */
  signal(SIGALRM, catch_alarm);

  /* set in alarm in 'time' seconds */
  alarm((unsigned)time_limit);

  /* print message into test log file */
  fprintf(TESTLOG,"Send alarm in %d seconds\n",time_limit);
  fprintf(TESTLOG,"\n====================================================\n\n");

}

#else

void set_alarm(FILE *LOG ATTRIBUTE__UNUSED) {;}

#endif

/*---------------------------------------------------------------------------*/
/* print header for test log file                                            */

void print_test_log_header( FILE *LOG, unsigned long seed, int fullcheck )
{
  time_t started;  
  
  /* Title */
  fprintf(LOG,"\nUNU.RAN - Universal Non-Uniform RANdom number generator\n\n");
  if (time( &started ) != -1)
    fprintf(LOG,"%s",ctime(&started));
  fprintf(LOG,"\n=======================================================\n\n");

  /* version */
  fprintf(LOG,"UNU.RAN version:        %s\n", PACKAGE_STRING);

  /* deprecated code */
  fprintf(LOG,"Use deprecated code:    %s\n",
#ifdef USE_DEPRECATED_CODE
	  "yes"
#else
	  "no"
#endif
	  );
  
  /* runtime checks */
  fprintf(LOG,"Enable runtime checks:  %s\n",
#if defined(UNUR_ENABLE_CHECKNULL) || defined(UNUR_COOKIES)
	  "yes"
#else
	  "no"
#endif
	  );

  /* printing debugging info into log file */
  fprintf(LOG,"Enable logging of data: %s\n",
#ifdef UNUR_ENABLE_LOGGING
	  "yes"
#else
	  "no"
#endif
	  );

  /* creating info string */
  fprintf(LOG,"Enable info routine:    %s\n",
#ifdef UNUR_ENABLE_INFO
	  "yes"
#else
	  "no"
#endif
	  );

  fprintf(LOG,"\n");

  /* Uniform random number generator */
  fprintf(LOG,"Uniform random number generator: %s\n",
#ifdef UNUR_URNG_DEFAULT_RNGSTREAM
	  "RngStreams"
#else
	  "[default]  (built-in or user supplied)"
#endif
	  );

  /* seed */
  if (seed != ~0u) 
    fprintf(LOG,"SEED = %lu\n",seed);
  else
    fprintf(LOG,"SEED = (not set)\n");
  
  fprintf(LOG,"\n");

  /* whether all checks are performed */
  fprintf(LOG,"check mode: %s\n",
	  fullcheck ? "fullcheck" : "installation");

  /* end */
  fprintf(LOG,"\n=======================================================\n\n");

} /* end of print_test_log_header() */

/*---------------------------------------------------------------------------*/
/* check for invalid NULL pointer, that should not happen in this program */

void abort_if_NULL( FILE *LOG, int line, const void *ptr )
{
  if (ptr) return; /* o.k. */
  
  /* 
     There must not be a NULL pointer.
     Since we do not expect a NULL pointer something serious has
     happend. So it is better to abort the tests.
  */
  fprintf(LOG,"line %4d: Unexpected NULL pointer. Panik --> Abort tests!!\n\n",line);
  printf(" Panik --> Tests aborted\n"); fflush(stdout);
  
  /* test finished */
  printf("\n");  fflush(stdout);
  
  /* close log files and exit */
  fclose(LOG);
  exit(EXIT_FAILURE);
  
} /* abort_if_NULL() */

/*---------------------------------------------------------------------------*/
/* compare error code */

int check_errorcode( FILE *LOG, int line, int errno_exp )
{
  int errno_obs = unur_get_errno();

  fprintf(LOG,"line %4d: Error code ...\t\t",line);

  if (errno_obs != errno_exp) {
    fprintf(LOG," Failed");
    fprintf(LOG," (observed = %#x, expected = %#x)\n",errno_obs,errno_exp);
    fflush(LOG);
    return UNUR_FAILURE;
  }

  else {
    fprintf(LOG," ok\n");
    fflush(LOG);
    return UNUR_SUCCESS;
  }
} /* end of check_errorcode() */

/*---------------------------------------------------------------------------*/
/* check for expected NULL pointer */

int do_check_expected_NULL( FILE *LOG, int line, int is_NULL )
{
  fprintf(LOG,"line %4d: NULL pointer expected ...\t",line);

  if (is_NULL) {
    fprintf(LOG," ok\n");
    fflush(LOG);
    return UNUR_SUCCESS;
  }

  else {
    fprintf(LOG," Failed\n");
    fflush(LOG);
    return UNUR_FAILURE;
  }

} /* end of check_expected_NULL() */

/*---------------------------------------------------------------------------*/
/* check for "set failed" */

int check_expected_setfailed( FILE *LOG, int line, int rcode )
{
  fprintf(LOG,"line %4d: `failed' expected ...\t",line);
  if (rcode==UNUR_SUCCESS) {
    fprintf(LOG," Failed\n");
    fflush(LOG);
    return UNUR_FAILURE;
  }

  else {
    fprintf(LOG," ok\n");
    fflush(LOG);
    return UNUR_SUCCESS;
  }
} /* end of check_expected_setfailed() */

/*---------------------------------------------------------------------------*/
/* check for O (zero) */

int check_expected_zero( FILE *LOG, int line, int k )
{
  fprintf(LOG,"line %4d: 0 (zero) expected ...\t",line);

  if (k != 0) {
    fprintf(LOG," Failed\n");
    fflush(LOG);
    return UNUR_FAILURE;
  }

  else {
    fprintf(LOG," ok\n");
    fflush(LOG);
    return UNUR_SUCCESS;
  }
} /* end of check_expected_zero() */

/*---------------------------------------------------------------------------*/
/* check for INFINITY */

int check_expected_INFINITY( FILE *LOG, int line, double x )
{
  fprintf(LOG,"line %4d: INFINITY expected ...\t",line);

  if (x < UNUR_INFINITY) {
    fprintf(LOG," Failed\n");
    fflush(LOG);
    return UNUR_FAILURE;
  }

  else {
    fprintf(LOG," ok\n");
    fflush(LOG);
    return UNUR_SUCCESS;
  }
} /* end of check_expected_INFINITY() */

int check_expected_negINFINITY( FILE *LOG, int line, double x )
{
  fprintf(LOG,"line %4d: -INFINITY expected ...\t",line);

  if (x > -UNUR_INFINITY) {
    fprintf(LOG," Failed\n");
    fflush(LOG);
    return UNUR_FAILURE;
  }

  else {
    fprintf(LOG," ok\n");
    fflush(LOG);
    return UNUR_SUCCESS;
  }
} /* end of check_expected_negINFINITY() */

int check_expected_INTMAX( FILE *LOG, int line, int k )
{
  fprintf(LOG,"line %4d: INT_MAX expected ...\t",line);

  if (k < INT_MAX) {
    fprintf(LOG," Failed\n");
    fflush(LOG);
    return UNUR_FAILURE;
  }

  else {
    fprintf(LOG," ok\n");
    fflush(LOG);
    return UNUR_SUCCESS;
  }
} /* end of check_expected_INTMAX() */

/*---------------------------------------------------------------------------*/
/* check for reinit */

int check_expected_reinit( FILE *LOG, int line, int rcode )
{
  fprintf(LOG,"line %4d: reinit ...\t\t\t",line);

  if (rcode!=UNUR_SUCCESS) {
    fprintf(LOG," Failed\n");
    fflush(LOG);
    return UNUR_FAILURE;
  }

  else {
    fprintf(LOG," ok\n");
    fflush(LOG);
    return UNUR_SUCCESS;
  }
} /* end of check_expected_reinit() */

/*---------------------------------------------------------------------------*/
/* check for non existing reinit */

int check_expected_no_reinit( FILE *LOG, int line, int rcode )
{
  fprintf(LOG,"line %4d: no reinit ...\t\t",line);

  if (rcode==UNUR_SUCCESS) {
    fprintf(LOG," Failed\n");
    fflush(LOG);
    return UNUR_FAILURE;
  }

  else {
    fprintf(LOG," ok\n");
    fflush(LOG);
    return UNUR_SUCCESS;
  }
} /* end of check_expected_no_reinit() */


/*---------------------------------------------------------------------------*/
/* draw sample */

int draw_sample_and_discard ( FILE *LOG, int line, UNUR_GEN *gen, int sample_size )
{
  /* int J; */
  /* double X; */
  double *vec;

  switch (unur_distr_get_type(unur_get_distr(gen)) ) {

  case UNUR_DISTR_DISCR:
    for( ; sample_size>0; --sample_size) 
      /* J = unur_sample_discr(gen); */
      unur_sample_discr(gen);
    return UNUR_SUCCESS;

  case UNUR_DISTR_CONT:
  case UNUR_DISTR_CEMP:
    for( ; sample_size>0; --sample_size) 
      /* X = unur_sample_cont(gen); */
      unur_sample_cont(gen);
    return UNUR_SUCCESS;

  case UNUR_DISTR_CVEC:
  case UNUR_DISTR_CVEMP:
    /* we need an array for the vector */
    vec = malloc( unur_get_dimension(gen) * sizeof(double) );
    abort_if_NULL(LOG, line, vec);
    for( ; sample_size>0; --sample_size) 
      unur_sample_vec(gen,vec);
    free(vec);
    return UNUR_SUCCESS;

  default: /* unknown ! */
    fprintf(stderr,"\ncannot handle distribution type! ... aborted\n");
    exit (EXIT_FAILURE);
  }

} /* end of unur_test_printsample() */

/*---------------------------------------------------------------------------*/
/* compare two doubles */

int
compare_doubles ( double x1, double x2 )
{
  if (_unur_FP_approx(x1,x2))
    return TRUE;
  else if (fabs(x1-x2) < UNUR_EPSILON)
    return TRUE;
  else
    return FALSE;
} /* end of compare_doubles() */

/*---------------------------------------------------------------------------*/
/* compare sequences generated by generator                                  */

int
compare_sequence_gen_start( FILE *LOG, int line, UNUR_GEN *gen, int sample_size )
{

  if (gen == NULL) return UNUR_FAILURE;

  /* reset uniform RNG */
  if (unur_urng_reset(unur_get_urng(gen)) != UNUR_SUCCESS) 
    return UNUR_SUCCESS;

  switch( unur_distr_get_type(unur_get_distr(gen)) ) {

  case UNUR_DISTR_CONT:
  case UNUR_DISTR_CEMP:
  case 0:  /* method UNIF */
    return compare_double_sequence_gen_start(LOG,line,gen,sample_size );

  case UNUR_DISTR_CVEC:
  case UNUR_DISTR_CVEMP:
    return compare_cvec_sequence_gen_start(LOG,line,gen,sample_size );

  case UNUR_DISTR_DISCR:
    return compare_int_sequence_gen_start(LOG,line,gen,sample_size );

  case UNUR_DISTR_MATR:
    return compare_matr_sequence_gen_start(LOG,line,gen,sample_size );

  default:
    fprintf(stderr,"\ncannot handle distribution type! ... aborted\n");
    exit (EXIT_FAILURE);
  }

} /* end of compare_sequence_gen_start() */

int 
compare_sequence_gen ( FILE *LOG, int line, UNUR_GEN *gen, int sample_size )
{
  if (gen == NULL) return UNUR_FAILURE;

  /* reset uniform RNG */
  if (unur_urng_reset(unur_get_urng(gen)) != UNUR_SUCCESS) 
    return cannot_compare_sequence(LOG);

  switch( unur_distr_get_type(unur_get_distr(gen)) ) {

  case UNUR_DISTR_CONT:
  case UNUR_DISTR_CEMP:
  case 0:  /* method UNIF */
    return compare_double_sequence_gen(LOG,line,gen,sample_size );

  case UNUR_DISTR_CVEC:
  case UNUR_DISTR_CVEMP:
    return compare_cvec_sequence_gen(LOG,line,gen,sample_size );

  case UNUR_DISTR_DISCR:
    return compare_int_sequence_gen(LOG,line,gen,sample_size );

  case UNUR_DISTR_MATR:
    return compare_matr_sequence_gen(LOG,line,gen,sample_size );

  default:
    fprintf(stderr,"\ncannot handle distribution type! ... aborted\n");
    exit (EXIT_FAILURE);
  }
} /* end of compare_sequence_gen() */

/*---------------------------------------------------------------------------*/

int
compare_sequence_par_start( FILE *LOG, int line, UNUR_PAR *par, int sample_size )
{
  UNUR_GEN *gen;
  int result;

  /* reset uniform RNG */
  if (unur_urng_reset(unur_get_default_urng()) != UNUR_SUCCESS) {
    if (par) {
      free(par);
    }
    return UNUR_SUCCESS;
  }

  /* init generator */
  gen = unur_init( par ); abort_if_NULL(LOG,line,gen);

  /* run test */
  result = compare_sequence_gen_start(LOG,line,gen,sample_size);

  unur_free(gen);
  return result;

} /* end of compare_sequence_par_start() */

int 
compare_sequence_par ( FILE *LOG, int line, UNUR_PAR *par, int sample_size )
{
  UNUR_GEN *gen;
  int result;

  /* reset uniform RNG */
  if (unur_urng_reset(unur_get_default_urng()) != UNUR_SUCCESS) {
    if (par) {
      free(par);
    }
    return cannot_compare_sequence(LOG);
  }

  /* init generator */
  gen = unur_init( par ); abort_if_NULL(LOG, line,gen);

  /* run test */
  result = compare_sequence_gen(LOG,line,gen,sample_size);
  
  unur_free(gen);

  return result;

} /* end of compare_sequence_par() */

/*---------------------------------------------------------------------------*/
/* compare double sequences generated by generator */
/* only when when we can reset the uniform RNG     */

static double *double_sequence_A = NULL;

int compare_sequence_urng_start( FILE *LOG, int line, int sample_size )
{
  int i;
  UNUR_URNG *urng = unur_get_default_urng();
  
  /* allocate memory for storing sequence */
  if (double_sequence_A == NULL) {
    double_sequence_A = malloc( sample_size * sizeof(double) );
    abort_if_NULL(LOG, line, double_sequence_A);
  }

  /* reset uniform RNG */
  if (unur_urng_reset(urng) != UNUR_SUCCESS) 
    return UNUR_SUCCESS;

  /* generate sequence */
  for (i=0; i<sample_size; i++)
    double_sequence_A[i] = unur_urng_sample(urng);

  /* there cannot be a failure */
  return UNUR_SUCCESS;

} /* end of compare_sequence_urng_start() */

/*...........................................................................*/

int compare_double_sequence_gen_start( FILE *LOG, int line, UNUR_GEN *gen, int sample_size )
{
  int i;

  /* check generator object */
  if (gen==NULL) {
    /* error */
    if (double_sequence_A) free (double_sequence_A);
    double_sequence_A = NULL;
    return UNUR_FAILURE;
  }

  /* allocate memory for storing sequence */
  if (double_sequence_A == NULL) {
    double_sequence_A = malloc( sample_size * sizeof(double) );
    abort_if_NULL(LOG,line, double_sequence_A);
  }

  /* generate sequence */
  for (i=0; i<sample_size; i++)
    double_sequence_A[i] = unur_sample_cont(gen);
  
  /* there cannot be a failure */
  return UNUR_SUCCESS;

} /* end of compare_double_sequence_gen_start() */

/*...........................................................................*/

int compare_double_sequence_gen( FILE *LOG, int line, UNUR_GEN *gen, int sample_size )
{
  int i;
  int ok = TRUE;
  double x = 0.;
  int failed = 0;

  /* check generator object and stored sequence */
  if (gen==NULL || double_sequence_A==NULL) {
    /* error */
    return UNUR_FAILURE;
  }

  /* compare sequence */
  for (i=0; i<sample_size; i++) {
    x = unur_sample_cont(gen);	
    if (!compare_doubles(double_sequence_A[i], x)) {
/*     if ( !_unur_FP_approx(double_sequence_A[i], x)) { */
      ok = FALSE;
      break;
    }
  }

  /* print result */
  fprintf(LOG,"line %4d: random seqences ...\t\t",line);
  if (!ok) {
    failed = 1;
    fprintf(LOG," Failed\n");
    fprintf(LOG,"\tx[1] = %g, x[2] = %g, diff = %g\n",double_sequence_A[i],x,double_sequence_A[i]-x);
  }
  else
    fprintf(LOG," ok\n");
  
  fflush(LOG);
  return (failed ? UNUR_FAILURE : UNUR_SUCCESS);

} /* end of compare_double_sequence_gen() */

/*---------------------------------------------------------------------------*/
/* compare int sequences generated by generator    */
/* only when when we can reset the uniform RNG     */

static int *int_sequence_A = NULL;

int compare_int_sequence_gen_start( FILE *LOG, int line, UNUR_GEN *gen, int sample_size )
{
  int i;

  /* check generator object */
  if (gen==NULL) {
    /* error */
    if (int_sequence_A) free (int_sequence_A);
    int_sequence_A = NULL;
    return UNUR_FAILURE;
  }

  /* allocate memory for storing sequence */
  if (int_sequence_A == NULL) {
    int_sequence_A = malloc( sample_size * sizeof(int) );
    abort_if_NULL(LOG, line, int_sequence_A);
  }
  
  /* generate sequence */
  for (i=0; i<sample_size; i++)
    int_sequence_A[i] = unur_sample_discr(gen);

  /* there cannot be a failure */
  return UNUR_SUCCESS;

} /* end of compare_int_sequence_gen_start() */

/*...........................................................................*/

int compare_int_sequence_gen( FILE *LOG, int line, UNUR_GEN *gen, int sample_size )
{
  int i;
  int ok = TRUE;
  int failed = 0;

  /* check generator object and stored sequence */
  if (gen==NULL || int_sequence_A==NULL) {
    /* error */
    return UNUR_FAILURE;
  }

  /* compare sequence */
  for (i=0; i<sample_size; i++)
    if (int_sequence_A[i] != unur_sample_discr(gen)) {
      ok = FALSE;
      break;
    }
  
  /* print result */
  fprintf(LOG,"line %4d: random seqences ...\t\t",line);
  if (!ok) {
    failed = 1;
    fprintf(LOG," Failed\n");
  }
  else
    fprintf(LOG," ok\n");
  
  fflush(LOG);
  return (failed ? UNUR_FAILURE : UNUR_SUCCESS);

} /* end of compare_int_sequence_gen() */

/*---------------------------------------------------------------------------*/
/* compare sequences of double vectors generated by generator */
/* only when when we can reset the uniform RNG                */

static double *cvec_sequence_A = NULL;

int compare_cvec_sequence_gen_start( FILE *LOG, int line, UNUR_GEN *gen, int sample_size )
{
  int i;
  int dim;

  /* free sequence */
  if (cvec_sequence_A) {
    free (cvec_sequence_A);
    cvec_sequence_A = NULL;
  }

  /* check generator object */
  if (gen==NULL) {
    /* error */
    return UNUR_FAILURE;
  }

  /* get dimension */
  dim = unur_get_dimension (gen);

  /* allocate memory for storing sequence */
  if (cvec_sequence_A == NULL) {
    cvec_sequence_A = malloc( dim * sample_size * sizeof(double) );
    abort_if_NULL(LOG,line, cvec_sequence_A);
  }

  /* generate sequence */
  for (i=0; i<sample_size; i++)
    unur_sample_vec( gen, cvec_sequence_A+(i*dim) );

  /* there cannot be a failure */
  return UNUR_SUCCESS;

} /* end of compare_cvec_sequence_gen_start() */

/*...........................................................................*/

int compare_cvec_sequence_gen( FILE *LOG, int line, UNUR_GEN *gen, int sample_size )
{
  int i,k;
  int ok = TRUE;
  double *x;
  int failed = 0;
  int dim;

  /* check generator object and stored sequence */
  if (gen==NULL || cvec_sequence_A==NULL) {
    /* error */
    return UNUR_FAILURE;
  }

  /* get dimension */
  dim = unur_get_dimension (gen);

  /* need space for sampling result */
  x = malloc( dim * sizeof(double) );
  abort_if_NULL(LOG,line, x);

  /* compare sequence */
  for (i=0; i<sample_size; i++) {
    unur_sample_vec( gen, x );
    for (k=0; k<dim; k++) {
      if ( _unur_FP_cmp(cvec_sequence_A[i*dim+k], x[k], 100.*UNUR_SQRT_DBL_EPSILON) !=0 ) {
	/* multivariate random variates are more sensitive against rounding effects */
	ok = FALSE;
	break;
      }
    }
    if (!ok) break;
  }

  /* print result */
  fprintf(LOG,"line %4d: random seqences ...\t\t",line);
  if (!ok) {
    failed = 1;
    fprintf(LOG," Failed\n");
    fprintf(LOG,"\tx[1] = (");
    for (k=0; k<dim; k++) 
      fprintf(LOG," %g",cvec_sequence_A[i*dim+k]);
    fprintf(LOG,"),\tx[2] = (");
    for (k=0; k<dim; k++) 
      fprintf(LOG," %g",x[k]);
    fprintf(LOG,"),\tdiff = (");
    for (k=0; k<dim; k++) 
      fprintf(LOG," %g",cvec_sequence_A[i*dim+k]-x[k]);
    fprintf(LOG,")\n");
  }
  else
    fprintf(LOG," ok\n");
  
  fflush(LOG);
  free (x);
  return (failed ? UNUR_FAILURE : UNUR_SUCCESS);

} /* end of compare_cvec_sequence_gen() */

/*---------------------------------------------------------------------------*/
/* compare sequences of double matrices generated by generator */
/* only when when we can reset the uniform RNG                */

static double *matr_sequence_A = NULL;

int compare_matr_sequence_gen_start( FILE *LOG, int line, UNUR_GEN *gen, int sample_size )
{
  int i;
  int dim;

  /* free sequence */
  if (matr_sequence_A) {
    free (matr_sequence_A);
    matr_sequence_A = NULL;
  }

  /* check generator object */
  if (gen==NULL) {
    /* error */
    return UNUR_FAILURE;
  }

  /* get dimension */
  dim = unur_get_dimension (gen);

  /* allocate memory for storing sequence */
  if (matr_sequence_A == NULL) {
    matr_sequence_A = malloc( dim * sample_size * sizeof(double) );
    abort_if_NULL(LOG,line, matr_sequence_A);
  }

  /* generate sequence */
  for (i=0; i<sample_size; i++)
    unur_sample_matr( gen, matr_sequence_A+(i*dim) );

  /* there cannot be a failure */
  return UNUR_SUCCESS;

} /* end of compare_matr_sequence_gen_start() */

/*...........................................................................*/

int compare_matr_sequence_gen( FILE *LOG, int line, UNUR_GEN *gen, int sample_size )
{
  int i,k;
  int ok = TRUE;
  double *x;
  int failed = 0;
  int dim;

  /* check generator object and stored sequence */
  if (gen==NULL || matr_sequence_A==NULL) {
    /* error */
    return UNUR_FAILURE;
  }

  /* get dimension */
  dim = unur_get_dimension (gen);

  /* need space for sampling result */
  x = malloc( dim * sizeof(double) );
  abort_if_NULL(LOG,line, x);

  /* compare sequence */
  for (i=0; i<sample_size; i++) {
    unur_sample_matr( gen, x );
    for (k=0; k<dim; k++) {
      if ( !compare_doubles(matr_sequence_A[i*dim+k], x[k])) {
/*       if ( !_unur_FP_approx(matr_sequence_A[i*dim+k], x[k])) { */
	ok = FALSE;
	break;
      }
    }
    if (!ok) break;
  }

  /* print result */
  fprintf(LOG,"line %4d: random seqences ...\t\t",line);
  if (!ok) {
    failed = 1;
    fprintf(LOG," Failed\n");
    fprintf(LOG,"\tx[1] = (");
    for (k=0; k<dim; k++) 
      fprintf(LOG," %g",matr_sequence_A[i*dim+k]);
    fprintf(LOG,"),\tx[2] = (");
    for (k=0; k<dim; k++) 
      fprintf(LOG," %g",x[k]);
    fprintf(LOG,"),\tdiff = (");
    for (k=0; k<dim; k++) 
      fprintf(LOG," %g",matr_sequence_A[i*dim+k]-x[k]);
    fprintf(LOG,")\n");
  }
  else
    fprintf(LOG," ok\n");
  
  fflush(LOG);
  free (x);
  return (failed ? UNUR_FAILURE : UNUR_SUCCESS);

} /* end of compare_matr_sequence_gen() */

/*---------------------------------------------------------------------------*/
/* free memory used for comparing sequences                                  */

void compare_free_memory( void )
{
  if (double_sequence_A) free (double_sequence_A);
  if (int_sequence_A)    free (int_sequence_A);
  if (cvec_sequence_A)   free (cvec_sequence_A);
  if (matr_sequence_A)   free (matr_sequence_A);
} /* end of compare_free_memory */

/*---------------------------------------------------------------------------*/
/* print name of distribution */

void print_distr_name( FILE *LOG, const UNUR_DISTR *distr, const char *genid )
{
  int i,n_fpar;
  const double *fpar;

  /* print name of distribution */
  if (genid)
    fprintf(LOG,"%s: ",genid);
  fprintf(LOG,"%s ",unur_distr_get_name(distr));

  /* get parameters */
  n_fpar = 0;
  if ( unur_distr_is_cont(distr) )
    /* continuous distribution */
    n_fpar = unur_distr_cont_get_pdfparams( distr, &fpar );
  else if ( unur_distr_is_discr(distr) )
    /* discrete distribution */
    n_fpar = unur_distr_discr_get_pmfparams( distr, &fpar );

  /* print parameter list */
  fprintf(LOG,"(");
  if (n_fpar) {
    fprintf(LOG,"%g",fpar[0]);
    for (i=1; i<n_fpar; i++)
      fprintf(LOG,", %g",fpar[i]);
  }
  fprintf(LOG,")");

} /* end of print_distr_name() */

/*---------------------------------------------------------------------------*/
/* check p-value of statistical test and print result */

int print_pval( FILE *LOG, UNUR_GEN *gen, const UNUR_DISTR *distr, 
		double pval, int trial, int todo )
{
  int failed = 0;
  double pval_corrected;
  int dim;
  int l;

  /* Correct p-value.   */
  /* For multivariate distributions unur_test_chi2() runs chi^2 tests on the   */
  /* multivariate distribution itself as well as on all marginal distibutions. */
  /* It returns the minimum of all p-palues. Thus we have to adjust this       */
  /* value and multiply it with (dim+1) before deciding about the significance */
  /* of the result.                                                            */
  dim = unur_distr_get_dim(distr);
  pval_corrected = (dim>1) ? pval : pval*(dim+1);


  if (pval < -1.5) {
    /* was not able to run test (CDF missing) */

    fprintf(LOG,"   not performed (missing data)\t");
    /* print distribution name */
    print_distr_name( LOG, distr, gen ? unur_get_genid(gen):"???\t" );
    fprintf(LOG,"\n");

    printf("X");

    fflush(stdout);
    fflush(LOG);

    /* test failed */
    return UNUR_FAILURE;
  }

  if (pval < 0.) {
    fprintf(LOG,"   setup failed\t\t");
  }
  else {
    fprintf(LOG,"   pval = %8.6f   ",pval);
    l = _unur_isnan(pval_corrected) 
      ? 10000
      : -(int) ((pval_corrected > 1e-6) ? (log(pval_corrected) / M_LN10) : 6.);
    switch (l) {
    case 0:
      fprintf(LOG,"      "); break;
    case 1:
      fprintf(LOG,".     "); break;
    case 2:
      fprintf(LOG,"**    "); break;
    case 3:
      fprintf(LOG,"XXX   "); break;
    case 4:
      fprintf(LOG,"XXXX  "); break;
    case 5:
      fprintf(LOG,"XXXXX "); break;
    default:
      fprintf(LOG,"######"); break;
    }
  }
  
  switch (todo) {
  case '+':
    if (pval_corrected >= PVAL_LIMIT) {
      fprintf(LOG,"\t ok");
      printf("+");
    }
    else {
      failed = 1;
      if (trial > 2) {
	fprintf(LOG,"\t Failed");
	printf("(!+)");
      }
      else {
	fprintf(LOG,"\t Try again");
	printf("?");
      }
    }
    break;
  case '0':
  case '/':
    fprintf(LOG,"\t ok (expected to fail)");
    printf("0");
    break;
  case '-':
    if (pval_corrected < PVAL_LIMIT) {
      /* in this case it is expected to fail */
      failed = 0;
      fprintf(LOG,"\t ok (expected to fail)");
      printf("-");
    }
    else {
      /* the test has failed which was not what we have expected */
      failed = 1;
      fprintf(LOG,"\t Not ok (expected to fail)");
      printf("(!-)");
    }
    break;
  default:
    fprintf(stderr, "%s:%d: invalid test symbol\n", __FILE__, __LINE__);
    exit (-1);
  }

  /* timing */
  stopwatch_print(LOG,"\ttime=%.1f ms ", stopwatch_lap(&vw));

  /* print distribution name */
  fprintf(LOG,"\t");
  print_distr_name( LOG, distr, gen ? unur_get_genid(gen):"???\t" );
  fprintf(LOG,"\n");

  fflush(stdout);
  fflush(LOG);
  return (failed ? UNUR_FAILURE : UNUR_SUCCESS);

} /* end of print_pval() */

/*---------------------------------------------------------------------------*/
/* run chi2 test */

int run_validate_chi2( FILE *LOG, int line ATTRIBUTE__UNUSED, 
		       UNUR_GEN *gen, const UNUR_DISTR *distr, int todo )
     /*   UNUR_SUCCESS    ... on success                                        */
     /*   UNUR_ERR_SILENT ... test failed only once                             */
     /*   UNUR_FAILURE    ... serious failure                                   */
{
#define BUFSIZE 128
  const char *distr_name;
  static char last_distr_name[BUFSIZE] = "";
  unsigned int type;
  int i;
  double pval;
  int failed = 0;

  /* check input */
  if (distr == NULL) {
    printf(" [!!no distribution!!] "); fflush(stdout);
    return UNUR_FAILURE;
  }

  /* timer */
  stopwatch_lap(&vw);

  /* get name of distribution */
  distr_name = unur_distr_get_name( distr );

  /* get type of distribution */
  type = unur_distr_get_type( distr );

  if ( strcmp(distr_name,last_distr_name) ) {
    /* different distributions */
    strncpy(last_distr_name,distr_name,(size_t)BUFSIZE);
    last_distr_name[BUFSIZE-1] = '\0';
    printf(" %s",distr_name); fflush(stdout);
  }

  if (todo == '.') {
    /* nothing to do */
    printf(".");  fflush(stdout);
    return UNUR_SUCCESS;
  }

  if (todo == '0') {
    /* initialization of generator is expected to fail */
    if (gen == NULL) {
      printf("0");  fflush(stdout);
      return UNUR_SUCCESS;
    }
    else {
      /* error */
      printf("(!0)");  fflush(stdout);
      return UNUR_FAILURE;
    }
  }

  if (gen == NULL) {
    if (todo == '-') {
      print_pval(LOG,gen,distr,-0.5,10,todo);
      return UNUR_SUCCESS;
    }
    else if (todo == '/') {
      printf("/");  fflush(stdout);
      return UNUR_SUCCESS;
    }
    else {
      /* initialization failed --> cannot run test */
      print_pval(LOG,gen,distr,-0.5,10,todo);
      return UNUR_FAILURE;
    }
  }

  if (todo == '%') {
    /* initialize generator and draw small sample.          */
    /* (for memory debugging when the CDF is not available) */
    printf("%%");  fflush(stdout);
    draw_sample_and_discard(LOG, line, gen, 100);
    fprintf(LOG,"   not performed (sample only)\t");
    print_distr_name( LOG, distr, gen ? unur_get_genid(gen):"???\t" );
    fprintf(LOG,"\n");
    return UNUR_SUCCESS;
  }

  /* init successful */
  if ( todo == '/' ) todo = '+';

  /* run chi^2 test */
  for (i=1; i<=3; i++) {
    /* we run the test up to three times when it fails */

    switch (type) {
    case UNUR_DISTR_DISCR:
      pval = unur_test_chi2( gen, CHI_TEST_INTERVALS, 100000, 20, CHI_TEST_VERBOSITY, LOG);
      break;
    case UNUR_DISTR_CONT:
    case UNUR_DISTR_CEMP:
      pval = unur_test_chi2( gen, CHI_TEST_INTERVALS, 0, 20, CHI_TEST_VERBOSITY, LOG);
      break;
    case UNUR_DISTR_CVEC:
    case UNUR_DISTR_CVEMP:
      pval = unur_test_chi2( gen, CHI_TEST_INTERVALS, 0, 20, CHI_TEST_VERBOSITY, LOG);
      break;
    default:
      fprintf(stderr, "\n%s:%d: run_validate_chi2: this should not happen\n", __FILE__, __LINE__);
      exit (EXIT_FAILURE);
    }

    if ( print_pval(LOG,gen,distr,pval,i,todo) != UNUR_SUCCESS )
      /* test failed */
      failed++;
    else
      /* test ok */
      break;
  }

  return (failed==0 ? UNUR_SUCCESS : (failed<=2 ? UNUR_ERR_SILENT : UNUR_FAILURE));

#undef BUFSIZE
} /* end of run_validate_chi2() */

/*---------------------------------------------------------------------------*/
/* run verify hat test */

#define VERIFYHAT_SAMPLESIZE 10000

int run_validate_verifyhat( FILE *LOG, int line, UNUR_GEN *gen, 
			    const UNUR_DISTR *distr, int todo )
{
#define BUFSIZE 128
  const char *distr_name;
  static char last_distr_name[BUFSIZE] = "";
  unsigned int type;
  int i;
  int failed = 0;
  int dim;
  double *x = NULL;
  int errno_obs;

  /* check input */
  if (distr == NULL) {
    printf(" [!!no distribution!!]" ); fflush(stdout);
    return UNUR_FAILURE;
  }

  /* timer */
  stopwatch_lap(&vw);

  /* get name of distribution */
  distr_name = unur_distr_get_name( distr );

  /* get type of distribution */
  type = unur_distr_get_type( distr );

  if (strcmp(distr_name,last_distr_name) ) {
    /* different distributions */
    strncpy(last_distr_name,distr_name,(size_t)BUFSIZE);
    last_distr_name[BUFSIZE-1] = '\0';
    printf(" %s",distr_name); fflush(stdout);
  }

  if (todo == '.') {
    /* nothing to do */
    printf(".");  fflush(stdout);
    return UNUR_SUCCESS;
  }

  if (todo == '0') {
    /* initialization of generator is expected to fail */
    if (gen == NULL) {
      printf("0");  fflush(stdout);
      print_verifyhat_result(LOG,gen,distr,-1,todo);
      return UNUR_SUCCESS;
    }
    else {
      /* error */
      printf("(!0)");  fflush(stdout);
      return UNUR_FAILURE;
    }
  }

  if (gen == NULL) {
    if (todo == '-') {
      printf("-");  fflush(stdout);
      print_verifyhat_result(LOG,gen,distr,-1,todo);
      return UNUR_SUCCESS;
    }
    else {
      /* initialization failed --> cannot run test */
      printf("(!+)");  fflush(stdout);
      print_verifyhat_result(LOG,gen,distr,-1,todo);
      return UNUR_FAILURE;
    }
  }

  /* get dimension of distribution */
  dim = unur_get_dimension (gen);
  
  /* allocate working space */
  if (dim > 0) {
    x = malloc( dim * sizeof(double) );
    abort_if_NULL(LOG, line, x);
  }

  /* run verify hat test */
  for (i=0; i<VERIFYHAT_SAMPLESIZE; i++) {

    unur_reset_errno();
    switch (type) {
    case UNUR_DISTR_DISCR:
      unur_sample_discr(gen);
      break;
    case UNUR_DISTR_CONT:
    case UNUR_DISTR_CEMP:
      unur_sample_cont(gen);
      break;
    case UNUR_DISTR_CVEC:
      unur_sample_vec(gen,x);
      break;
    default:
      fprintf(stderr, "\n%s:%d: run_validate_verifyhat: this should not happen\n", __FILE__, __LINE__);
      exit (EXIT_FAILURE);
    }

    if ((errno_obs = unur_get_errno())) {
      /* error */
      if (errno_obs == UNUR_ERR_GEN_CONDITION)
	failed++;
      unur_reset_errno();
    }
    
  }
  
  /* free working space */
  if (x!=NULL) free(x);

  return print_verifyhat_result(LOG,gen,distr,failed,todo);
  
} /* end of run_validate_verifyhat() */

/*---------------------------------------------------------------------------*/
/* print result of verify hat test */

int print_verifyhat_result( FILE *LOG, UNUR_GEN *gen, const UNUR_DISTR *distr,
			    int failed, int todo )
{
  int failed_test = 0;
  double failed_ratio = ((double)failed) / VERIFYHAT_SAMPLESIZE;

  if (failed >= 0) {
    fprintf(LOG,"   failures = %d (%g%%)  ",failed, 100. * failed_ratio);
    switch (todo) {
    case '+':
      if (failed > 0) {
	fprintf(LOG,"\t Failed");
	printf("(!+)");
	failed_test = 1;
      }
      else {
	fprintf(LOG,"\t ok");
	printf("+");
      }
      break;
    case '~':
      if (failed == 0) {
	fprintf(LOG,"\t ok");
	printf("+");
      }
      else if (failed_ratio <= 0.01) {
	fprintf(LOG,"\t tolerated");
	printf("(~+)");
      }
      else {
	fprintf(LOG,"\t Failed");
	printf("(!~+)");
	failed_test = 1;
      }
      break;
    case '-':
      if (failed_ratio > 0.01) {
	/* in this case it is expected to fail */
	fprintf(LOG,"\t ok (expected to fail)");
	printf("-");
      }
      else {
	/* the test has failed which was not what we have expected */
	fprintf(LOG,"\t Not ok (expected to fail)");
	printf("(!-)");
	failed_test = 1;
      }
      break;
    default:
      fprintf(stderr,"%s:%d: invalid test symbol\n", __FILE__, __LINE__);
      exit (EXIT_FAILURE);
    }
  }

  else {
    fprintf(LOG,"   setup failed\t");
    switch (todo) {
    case '0':
    case '-':
      fprintf(LOG,"\t ok (expected to fail)");
      break;
    default:
      fprintf(LOG,"\t Failed");
    }
  }

  /* timing */
  stopwatch_print(LOG,"\ttime=%.1f ms ", stopwatch_lap(&vw));

  /* print distribution name */
  fprintf(LOG,"\t");
  print_distr_name( LOG, distr, gen ? unur_get_genid(gen):"???\t" );
  fprintf(LOG,"\n");

  fflush(stdout);
  fflush(LOG);

  return (failed_test ? UNUR_ERR_SILENT : UNUR_SUCCESS);

} /* end of print_verifyhat_result() */
#undef VERIFYHAT_SAMPLESIZE

/*---------------------------------------------------------------------------*/
/* print result of timings */

void print_timing_results( FILE *LOG, int line ATTRIBUTE__UNUSED, const UNUR_DISTR *distr,
			   double *timing_setup, double *timing_marginal, int n_results )
{
  const char *distr_name;
  static const char *last_distr_name = "";
  int i;

  /* get name of distribution */
  distr_name = unur_distr_get_name( distr );

  if (strcmp(distr_name,last_distr_name) ) {
    /* different distributions */
    last_distr_name = distr_name;
    printf(" %s=",distr_name); fflush(stdout);
  }
  else {
    printf("="); fflush(stdout);
  }
  
  /* print timings into log file */
  for (i=0; i<n_results; i++)
    if (timing_marginal[i] < 0.)
      /* no test */
      fprintf(LOG, "      /        ");
    else
      fprintf(LOG, "%5.0f /%7.2f ", timing_setup[i], timing_marginal[i]);

  /* print name of distribution into log file */
  fprintf(LOG,"\t");
  print_distr_name( LOG, distr, NULL );
  fprintf(LOG,"\n");

} /* end of print_timing_results() */

/*---------------------------------------------------------------------------*/
/* run test for u-error of inversion method and print results                */

int run_validate_u_error( FILE *LOG, UNUR_GEN *gen, const UNUR_DISTR *distr,
			  double u_resolution, int samplesize )
{
  double maxerror, MAE;  /* maximum observed and mean absolute u-error */
  double score;          /* penalty score of inversion error test */
  const char *genid;

  /* check objectst */
  if (gen == NULL || distr == NULL) {
    fprintf(LOG,"test_inverror:   ERROR: generator not initialized\n");
    printf("(!!+)"); fflush(stdout);
    return 1000;
  }

  /* get generator id */
  genid = unur_get_genid(gen);

  /* run test */
  score = unur_test_u_error(gen, &maxerror, &MAE, u_resolution, samplesize, 
			     FALSE, TEST_INVERROR_TAILS, TRUE, LOG);

  /* check score */
  if (score < 0.) {
    /* an error has occured */
    fprintf(LOG,"%s:   ERROR: could not run test\n",genid);
    printf("(!!+)"); fflush(stdout);
    return 3;
  }

  /* print result into log file */
  fprintf(LOG,"%s:   result: max u-error = %g, MAE = %g  ",genid,maxerror,MAE);
  if (maxerror > u_resolution) {
     fprintf(LOG,"(NOT <= %g)\n",u_resolution);
     fprintf(LOG,"%s: Warning: precision problem, maxerror > u_resolution\n",genid);
  }
  else {
     fprintf(LOG,"(<= %g)\n",u_resolution);
  }

  fprintf(LOG,"%s:           score = %5.2f %%\t--> %s\n", genid,
	  score*100, (score<0.001) ? "passed" : "failed");
  
  /* print result of test on screen and return result */
  if (score == 0.) {
    printf("+"); fflush(stdout);
    return 0;
  }
  if (score < 0.001) {
    printf("(?+)"); fflush(stdout);
    return 1;
  }     
  else {
    printf("(!+)"); fflush(stdout);
    return 2;
  }

} /* end of run_validate_u_error() */

/*---------------------------------------------------------------------------*/
