/*
 # gcc -O4 -Wall -o mng mng.c -lm
 #
 # mng.c, Sun May 18 09:09:15  2003
 #
 #
 # This is a NG for sequences with fractal context representation
 #
 # M. Strickert and B. Hammer, Self-Organizing Context Learning. 
 # In: M.Verleysen (ed.), 
 #   European Symposium on Artificial Neural Networks (ESANN 2004),
 #   D-side publications, pp. 39-44, 2004
 #
 # -999.99 in pattern indicates context reset
 #
 */



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


#include "mng.h"


/* simulate -log(x) * x by fitted polynomial */
inline double shannon_fake(double x) { 
 return 2.065*x*x*x - 4.82*x*x + 2.76*x;
/*   return (x <= 0.) ? 0 : -log(x)*x; */
}



double entropy(void) {   /* both normalize histogram and entropy */

  int i;
  double inv_ta = 1. / total_activation, sum = 0.;

  inc_activation *= inv_ta;
  total_activation = 1.;

  for(i = 0; i < neurons_no; i++) {

    sum += shannon_fake(neurons[i] -> activation *= inv_ta);

    fprintf(stderr, "#A %d %g\n", neurons[i] -> idx, neurons[i] -> activation);
  }
  fprintf(stderr, "#A\n#A\n");


  return sum;
}




void neuron_free(NEURON n)
{
  free(n -> coord);
  free(n -> context);
  free(n);
}



void neuronsfree()
{

  while(neurons_lastindex >= 0)
    neuron_free(neurons[neurons_lastindex--]);

  free(neurons);

  neurons = NULL;

  neurons_lastindex = 0;
}



NEURON neuron_new_empty()
{

  NEURON n = (NEURON)malloc(sizeof(NEURON_TYPE));

  if(n == NULL) panic("neuron_new_empty(): cannot malloc neuron!");

  if(neurons_lastindex == 0 &&
      (neurons =
         (NEURON *)malloc(sizeof(NEURON) * (neurons_no+1))) == NULL)

      panic("neuron_new_empty(): cannot malloc neurons!");


  n -> idx = neurons_lastindex;

  neurons[neurons_lastindex++] = n; /* XXX: very courageous here */

  return n;
}



NEURON neuron_new(void)
{

  NEURON n = neuron_new_empty();

  if((n -> coord = (double *)malloc(sizeof(double) * pattern_dimension)) == NULL)
    panic("neuron_new(): cannot malloc coord!");

  if((n -> context = (double *)malloc(sizeof(double) * pattern_dimension)) == NULL)
    panic("neuron_new(): cannot malloc context!");

  n -> dist = 0.;
  n -> activation = 0.;

  return n;
}


/*** Distances ****************************************************************/

inline double neuron_patterndistance(NEURON n, double *pattern)
{

  int i = pattern_dimension;

  double *w = n -> coord, res = 0., tmp;

  while(--i >= 0) tmp = w[i] - pattern[i], res += tmp * tmp;

  return res;
}



inline double neuron_contextdistance(NEURON n, NEURON l)
{

  int i = pattern_dimension;

  double *c = n -> context, res = 0., tmp;

  while(--i >= 0) tmp = c[i] - MIX_CONTEXT_WEIGHT(l,i), res += tmp * tmp;

  return res;
}


inline int neuron_dists_cmp(void const *a1, void const *a2) {

  static double d1, d2;

  if((d1 = (*(NEURON *)a1) -> dist) < (d2 = (*(NEURON *)a2) -> dist))
    return -1;
  else if(d1 > d2)
    return 1;

  return 0;
}



/*
 * take all prototypes, calc distances to
 * presented pattern, sort correct and rank.
 * With speedups (mode-dependent).
 *
 * return number of used prototypes
 */

int neuron_prepare_by_pattern(
    double *pattern, NEURON **protos, int mode)
{

  double min = DBL_MAX, d;

  int i, no = 0;

  NEURON n, *result;


  if(**protos == NULL &&
       (*protos=(NEURON *)malloc(sizeof(NEURON) * neurons_no)) == NULL)
    panic("neuron_prepare_by_pattern(): cannot malloc() result");


  result = *protos;


  for(i = 0; i < neurons_no;  i++) {

    n = neurons[i];

    if(n == lastwinner) continue;  /* postsynaptic inhibition added: 26.11.03 */

    n -> dist = d = (1. - context_eta) *  neuron_patterndistance(n, pattern) +
                          context_eta  * neuron_contextdistance(n, lastwinner);


    if(no == 0) {
      min = d, result[0] = n;
      ++no; continue;
    }

    if(mode == 0) {
      if(d < min)
        min = d, result[0] = n;
      continue;
    }


    if(mode == 1) {

      if(no > 4 && d >= result[4] -> dist) continue;

      if(no == 1) {
        if(d < min) {
          result[1] = result[0];
          min = d, result[0] = n;
        }
        else
          result[1] = n;
        ++no; continue;
      }

      if(no == 2) {

        if(d < result[1] -> dist) {

          result[2] = result[1];

          if(d < min)
            result[1] = result[0], result[0] = n, min = d;
          else
            result[1] = n;
        }
        else
          result[2] = n;

        ++no; continue;
      }

      if(no == 3) {

        if(d < result[1] -> dist) {

          result[3] = result[2], result[2] = result[1];

          if(d > min)
            result[1] = n;
          else
            result[1] = result[0], result[0] = n, min = d;
        }
        else {
          if(d < result[2] -> dist)
            result[3] = result[2], result[2] = n;
          else
            result[3] = n;
        }

        ++no; continue;
      }

      if(no >= 4) { /* binary insert */

        if(d > result[2] -> dist) {

          if(d > result[3] -> dist)
            result[4] = n;
          else
            result[4] = result[3], result[3] = n;
        }
        else { /* d <= result[2] -> dist */

          result[4] = result[3], result[3] = result[2];

          if(d > result[1] -> dist)
            result[2] = n;
          else if(d > min)
            result[2] = result[1], result[1] = n;
          else
            result[2] = result[1], result[1] = result[0], result[0] = n,
            min = d;
        }

        no = 5; continue;
      }
    }

    /* mode >= 2 */
    result[no++] = n;
  }

  if(mode >= 2)                 /* sort result's prototypes */
    qsort(result, no, sizeof(NEURON), neuron_dists_cmp);

  return no;
}



void neuron_update(double *pattern, NEURON *winner, int no)
{

  NEURON n;

  int i, j /* , hasctx = lastwinner != neurons[neurons_no] */ ;

  double adapt, *coord, *context, r1, r2;


  (*winner) -> activation += inc_activation;
  total_activation += inc_activation;


  for(i = 0; i < no; i++) {

    adapt = i/neighborhood_size;

    if(adapt > NEIGSPEEDTHRESH && i != 0) break;

    n = *winner++;

    adapt = rate_eta * exp(-adapt);

    r2 = adapt * rate_context;
    r1 = adapt - r2;

    coord = n -> coord;

    context = n -> context;


    for(j = 0; j < pattern_dimension; j++) {

      coord[j] += r1 * (pattern[j] - coord[j]);

     /*  if(hasctx || i == 0) */
        context[j] += r2 * (MIX_CONTEXT_WEIGHT(lastwinner,j) - context[j]);
    }
  }
}


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



/* get next input vector from standard input */
/* XXX result vector allocated but never freed */

double * pattern_stdin(void)
{

  static int lnr;             /* (why?) line number not in use */
  static double *res = NULL;

  int column;

  ++lnr;

  if(res == NULL) {

    if((column=fgetc(stdin)) != '#')  /* file starts with # */
      ungetc(column, stdin);

    if(1 != scanf("%d", &pattern_dimension))
      error("main(): Could not read proper table dimension !");

    res = (double *)malloc(pattern_dimension * sizeof(double));

    if(res == NULL)
      panic("pattern_stdin(): Trouble allocating result vector!");

    return res;
  }

  if(1 != scanf("%lf", res)) return NULL;

  if(*res <= -999.99) {
    lastwinner = neurons[neurons_no];        /* reset context */
    if(1 != scanf("%lf", res)) return NULL;  /* read next symbol */
  }

  for(column = 1; column < pattern_dimension; column++)
    if(1 != scanf("%lf", res+column))
      return NULL;

  return res;
}



void neuron_stdout(NEURON n)
{

  int i;

  printf("%d ", n -> idx);

  for(i = 0; i < pattern_dimension; i++)
    printf("%g ", n -> coord[i]);

  for(i = 0; i < pattern_dimension; i++)
    printf("%g%c", n -> context[i], (i < pattern_dimension-1) ? ' ' : '\n');
}



void mng_print(void)
{
  int i;

  printf("%d %d %d %g %g %.12f %g %g %u\n", total_iterations, moduloprint, neurons_no, rate_eta, neighborhood_size, rate_neighborhood_annealing, mix_c_w, context_eta, randseed);

  for(i = 0; i < neurons_no; i++)
    neuron_stdout(neurons[i]);

  printf("\n\n"); fflush(stdout);
}



void mng_init(void)  /* assuming zero-centered data */
{

  int p, i;

  NEURON neuron;


  for(p = 0; p <= neurons_no; p++) { /* == neurons_no => lastwinner default */

    neuron = neuron_new();

    for(i = 0; i < pattern_dimension; i++) {

      neuron -> coord[i] = (p < neurons_no) ? 2. * (frand() - .5) : 0.;
      neuron -> context[i] = 0.;  /* trial: unbiased context */

    }
  }


  inc_activation_inc = pow(neurons_no, 1. / HISTDEC);

  if(randseed != 0)
    srand(randseed);
  else
    randseed = randomize();

  if(neighborhood_size < 0.)
    neighborhood_size = (double)neurons_no;
  else if(neighborhood_size == 0.)
    neighborhood_size = NEIGEPS;

  lastwinner = NULL;

  total_activation = 1.;
  inc_activation = 1. / neurons_no;

  training_cycles = 0;
}


void mng_train(int cycles)
{

  static NEURON nullneuron = NULL;

  NEURON *winner = &nullneuron;

  int no, dir = 0, entrosign = 1 == 1 /* , noctx = 0 */ ;

  double *pat, entro, oldentropy = 0., context_inceta = .005
         /* , savctxeta = 0. */ ;


  if(lastwinner == NULL)
    lastwinner = neurons[neurons_no];   /* dunno better */


  for(; cycles > 0; cycles--, ++training_cycles) {

    if(moduloprint > 0 && cycles % moduloprint == 0) {
      mng_print();
      fprintf(stderr, "%d %g\n", cycles, neighborhood_size);
    }


    pat = pattern_stdin();

    if(pat == NULL) break;     /* no more patterns */

/*    noctx = lastwinner == neurons[neurons_no];
//
//    if(noctx) {            
//      savctxeta = context_eta;
//      context_eta = 0.;
//    }
*/

    no = neuron_prepare_by_pattern(pat, &winner,
          (neighborhood_size * NEIGSPEEDTHRESH < 4.) ? 1 : 2); /* XXX */

/* boost first pattern matcher */
/*     if(noctx) {
//       lastwinner = *winner;    
//       context_eta = savctxeta;
//     }
*/
    neuron_update(pat, winner, no);

    lastwinner = *winner;

    if(neighborhood_size > NEIGEPS)
      neighborhood_size *= rate_neighborhood_annealing;


    if((training_cycles+1) % neurons_no == 0)
      inc_activation *= inc_activation_inc;

    if((training_cycles+1) % (neurons_no << 5) == 0) {

      entro = entropy();

fprintf(stderr, "# entro: %g %g %g\n", entro, context_eta, inc_activation);

      if(context_eta > 0.) {   /* only if not zero: 0 means: dont adapt */

        /* changed 20.11.03 -->> */
        if(oldentropy > entro) {
          if(!dir) {  /* first time being worse than before */
            context_inceta = (context_inceta < 0) ? .005 : -.005;  /* switch direction once */
            dir = 1;
          }
        }
        else {
          if(dir) {
/*            context_inceta = (context_inceta < 0) ? .005 : -.005; */ /* switch direction */
            dir = 0;
          }
        }
        /* <<-- */

        /* before:
          if(oldentropy > entro)
              context_inceta = (context_inceta < 0) ? .005 : -.005;
        */

        context_eta += context_inceta;

        if(context_eta <= 0.)  {
          context_eta = 1e-10;
          context_inceta = .005;
        }

        if(context_eta > .995) {
          context_eta = .995;
          context_inceta = -.005;
        }

        if(entrosign == entro > oldentropy) {
          if(context_inceta < .05 && context_inceta > -.05)
            context_inceta *= CONTEXT_ETA_MUL_INC;  /* larger steps */
        }
        else {
          if(context_inceta > .001 && context_inceta < -.001)
            context_inceta *= CONTEXT_ETA_MUL_SUB;  /* smaller steps */
        }
      }

      oldentropy = entro;

      entrosign = entro > oldentropy;

    }
  }

  mng_print();


  /* use overhanging data for evaluation of dynamics */

  printf("# testing ...\n");

  lastwinner = neurons[neurons_no];

  while((pat = pattern_stdin()) != NULL) {

    for(cycles = 0; cycles < pattern_dimension; cycles++)
      printf("%g ", pat[cycles]);

    neuron_prepare_by_pattern(pat, &winner, 0);

    lastwinner = *winner;

    neuron_stdout(lastwinner);
  }
}



/******************************************************************************/
/* no much checking done for main argument evaluation                         */
/******************************************************************************/


int main(int argc, char **argv)
{


  total_iterations = (argc >= 2) ? atoi(argv[1]) : 1000;
  moduloprint = (argc >= 3) ? atoi(argv[2]) : 0;
  neurons_no = (argc >= 4) ? atoi(argv[3]) : 100;
  rate_eta =   (argc >= 5) ? atof(argv[4]) : 0.01;
  neighborhood_size =   (argc >= 6) ? atof(argv[5]) : -1;
  rate_neighborhood_annealing = (argc >= 7) ? atof(argv[6]) : 1;
  mix_c_w = (argc >= 8) ? atof(argv[7]) : .5;
  context_eta = (argc >= 9) ? atof(argv[8]) : 0;
  randseed = (argc >= 10) ? atof(argv[9]) : 0;

  rate_context = .5;

  pattern_stdin(); /* read pattern dimension */


  mng_init();
  mng_train(total_iterations);


  return 0;
}
