/* K M Briggs covariance.c 2001 April 24; 2005 Sep 12

   Covariance and correlation of a (possibly large) set of row vectors
   (online algorithm; no storage of all data in RAM is required).
   mu is the mean vector and zeta is the matrix of sum of products 
   of deviations from the mean.
   So, if m input vectors have been read, we have

      mu   = (1/m) Sum_{k=1}^{m} x[k]
      zeta =       Sum_{k=1}^{m} (x[k]-mu)(x[k]-mu)
      (i.e. zeta[i][j] = Sum_{k=1}^{m} (x[k][i]-mu[i])(x[k][j]-mu[j])),

   where x[k] is the kth data vector.
   From the zeta matrix everything can be calculated.

   Input file format:
     x[1][1] x[1][2] x[1][3] ... x[1][n]
     x[2][1] x[2][2] x[2][3] ... x[2][n]
     ...
     x[m][1] x[m][2] x[m][3] ... x[m][n]
   n can be specified on the command line (default n=1)
   The program will keep reading as long as data is coming in,
   so it can be used in a pipeline.

   Compile:
     gcc -O3 -Wall covariance.c -o covariance -lm
   Usage:
     ./covariance -h
     ./covariance < datafile
     or
     cat datafile | ./covariance [2>/dev/null]
   Backup:
     mcopy -o covariance.c a:
*/

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

#define MAXN 100
#define BUFSIZE 10000 /* maximum expected characters in a line */

unsigned int parseline(char *line, char **array, int maxstrings);

int main(int argc, char *argv[]) {
  unsigned int i,j,nstrings,m=0,n=0,firstline=1,szdb=sizeof(double);
  double f,g,xi,*mu=NULL,*delta=NULL,**zeta=NULL;
  char line[BUFSIZE],*array[MAXN],*endptr[1];
  const char *version=" "__DATE__" "__TIME__;
  if (argc>1) {
    if (argv[1][0]=='-' && argv[1][1]=='h')
      fprintf(stderr,"Usage: %s < datafile\n",argv[0]), exit(0);
  }
  fprintf(stderr,"%s: K M Briggs %s MAXN=%u\n",argv[0],version,MAXN);
  while (fgets(line,BUFSIZ,stdin)) {
    if ((nstrings=parseline(line,array,MAXN))) {
      if (nstrings<1 || (!firstline && nstrings<n))
	fprintf(stderr,"Too few items on input line\n"), exit(1);
      if (nstrings>MAXN || (!firstline && nstrings>n))
	fprintf(stderr,"Too many items on input line\n"), exit(2);
      if (firstline) {
        fprintf(stderr,"%s: n=%u columns\n",argv[0],n=nstrings);
        mu   =(double* )malloc(n*szdb);
        delta=(double* )malloc(n*szdb);
        zeta =(double**)malloc(n*szdb);
        for (i=0; i<n; i++) {
          zeta[i]=(double*)malloc((i+1)*szdb);
          for (j=0; j<=i; j++) zeta[i][j]=0.0;
          mu[i]=0.0;
        }
        firstline=0;
      } 
      m++;
      f=(m-1.0)/m;
      for (i=0; i<n; i++) {
        xi=strtod(array[i],endptr);
        if (array[i]==*endptr)
          fprintf(stderr,"Bad character in input: %s\n",array[i]), exit(1);
        delta[i]=xi-mu[i];
        mu[i]+=delta[i]/m;
        g=f*delta[i];
        for (j=0; j<=i; j++) zeta[i][j]+=g*delta[j];
      }
    }
  }
  fprintf(stderr,"%d rows read\n",m);
  fprintf(stderr,"means of columns:\n");
  for (i=0; i<n; i++) printf("% 10.3e ",mu[i]);
  printf("\n");
  if (m<2) fprintf(stderr,"not enough data\n"), exit(1);
  fprintf(stderr,"standard deviations of columns:\n");
  f=1.0/(m-1);
  for (i=0; i<n; i++) printf("% 10.3e ",sqrt(f*zeta[i][i]));
  printf("\n");
  fprintf(stderr,"covariance matrix:\n");
  for (i=0; i<n; i++) {
    for (j=0; j<=i; j++) printf("% 10.3e ",f*zeta[i][j]);
    printf("\n");
  }
  fprintf(stderr,"correlation matrix:\n");
  for (i=0; i<n; i++) {
    f=zeta[i][i];
    for (j=0; j<i; j++) printf("%10.5f ",zeta[i][j]/sqrt(f*zeta[j][j]));
    printf("   1.00000\n");
  }
  return 0;
}

unsigned int parseline(char *line, char **array, int maxstrings) {
  int nstrings=0;     /* number of strings read in */
  int qchar;          /* quote character */
  while (isspace(*line)) line++;
  if (line[0]=='#') return 0;
  while (*line) {
    qchar=(*line=='#')?*line++:'\0';
    array[nstrings]=line;
    while (*line && (qchar?(*line!=qchar):(!isspace(*line)))) line++;
    if (*line) *line++='\0';
    while (isspace(*line)) line++;
    if (++nstrings==maxstrings) return (maxstrings+(*line?1:0));
  }
  return nstrings;
}
