/* K M Briggs 2003 Oct 07
  
   Fit least-squares polynomial without storing the data
  
   Usage: 
     lsqpoly [n=1] < datafile
     Each line of datafile has an x,y pair and an optional weight
  
   Comparison tests:
     dm x2 1 x1 x1*x1 < inf | regress
     cat inf | nlr x1 y1 p3 "a-b*x+c*x^2" 0.2 0.2 2
*/

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

#define MAXSTRINGS 3
#define BUFSIZE 100

const int verbose=0;

int parseline(char *line, char **array, int maxstrings);
extern void clear_(int* NP,int* NRBAR,double D[],double RBAR[],double THETAB[],double* SSERR,int* IER);
extern void includ_(int* NP, int* NRBAR, double* WEIGHT, double XROW[], double* YELEM, double D[], double RBAR[], double THETAB[], double* SSERR, int* IER);
extern void tolset_(int* NP, int* NRBAR, double D[], double RBAR[], double* TOL, double WORK[], int* IER);
extern void sing_(int* NP, int* NRBAR, double D[], double RBAR[], double THETAB[], double* SSERR, double TOL[], int LINDEP[], double WORK[], int* IER);
extern void regcf_(int* NP, int* NRBAR, double D[], double RBAR[], double THETAB[], double TOL[], double BETA[], int* NREQ, int* IER);
extern void cov_(int* np, int* nrbar, double d[], double rbar[], int* nreq, double* rinv, double* var, double covmat[], int* dimcov, double sterr[], int* ier);

int main (int argc, char* argv[]) {
  int n=1,nstrings,np,nrbar,dimcov,i,j,k,ier,nreq,nobs=0,*lindep;
  double x,y,w,sserr,var;
  double *d,*rbar,*thetab,*xrow,*tol,*work,*beta,*covmat,*rinv,*sterr;
  char line[BUFSIZE],*array[MAXSTRINGS],*endptr[1];
  const char *version=" "__DATE__" "__TIME__;
  if (argc>1) {
    if (argc>2 || 
       (argv[1][0]=='-' && argv[1][1]=='h') || 
       (argv[1][0]=='-' && argv[1][1]=='-' && argv[1][2]=='h')) {
         fprintf(stderr,"Usage: %s [n=1] < datafile\n",argv[0]);
         fprintf(stderr,"Each line of datafile has an x,y pair and an optional weight\n");
	 exit(0);
    }
    n=atoi(argv[1]);
  }
  n=n<1?1:n;
  fprintf(stderr,"%s: K M Briggs %s degree=%d\n",argv[0],version,n);
  nreq=np=n+1;
  nrbar=np*(np-1)/2;
  dimcov=np*(np+1)/2;
  lindep=malloc(np*sizeof(int));
  d     =malloc(np*sizeof(double));
  thetab=malloc(np*sizeof(double));
  xrow  =malloc(np*sizeof(double));
  tol   =malloc(np*sizeof(double));
  work  =malloc(np*sizeof(double));
  beta  =malloc(np*sizeof(double));
  sterr =malloc(np*sizeof(double));
  covmat=malloc(np*sizeof(double));
  rinv  =malloc(np*sizeof(double));
  rbar  =malloc(nrbar*sizeof(double));
  clear_(&np,&nrbar,d,rbar,thetab,&sserr,&ier);
  while (fgets(line,BUFSIZ,stdin)) {
    if ((nstrings=parseline(line,array,MAXSTRINGS))) {
      if (nstrings<2 || nstrings>3) 
	fprintf(stderr,"Wrong number of items on input line\n"), exit(1);
      x=strtod(array[0],endptr);
      if (array[0]==*endptr)
        fprintf(stderr,"Bad character in input: %s\n",array[0]), exit(1);
      y=strtod(array[1],endptr);
      if (array[1]==*endptr)
        fprintf(stderr,"Bad character in input: %s\n",array[1]), exit(1);
      if (nstrings==3) {
        w=strtod(array[2],endptr);
        if (array[2]==*endptr)
          fprintf(stderr,"Bad character in input: %s\n",array[2]), exit(1);
      } else w=1;
      if (verbose) printf("data read: x=%g y=%g w=%g\n",x,y,w);
      for (i=0; i<np; i++) xrow[i]=pow(x,i);
      includ_(&np,&nrbar,&w,xrow,&y,d,rbar,thetab,&sserr,&ier);
      if (ier!=0) printf("i=%d includ returns ier=%d\n",i,ier), exit(1);
      nobs++;
      if (nobs%1000000==0) fprintf(stderr,"%d data points read so far\n",nobs);
    }
  }
  tolset_(&np,&nrbar,d,rbar,tol,work,&ier);
  if (ier!=0) fprintf(stderr,"tolset returns ier=%d\n",ier), exit(1);
  sing_(&np,&nrbar,d,rbar,thetab,&sserr,tol,lindep,work,&ier);
  if (ier!=0) fprintf(stderr,"sing returns ier=%d\n",ier), exit(1);
  if (verbose) printf("sserr=%g\n",sserr);
  for (i=0; i<np; i++) 
    if (fabs(tol[i])>1e-9) printf("warning: tol[%d]=%.3g\n",i,tol[i]);
  regcf_(&np,&nrbar,d,rbar,thetab,tol,beta,&nreq,&ier);
  if (ier!=0) fprintf(stderr,"regcf returns ier=%d\n",ier), exit(1);
  printf("%d data points read\n",nobs);
  printf("least-squares polynomial:  % #.12g",beta[0]);
  if (n>0) { 
    if (beta[1]>0) printf(" + %.12g*x", beta[1]);
    else           printf(" - %.12g*x",-beta[1]); 
  }
  for (i=2; i<np; i++) {
    if (beta[i]>0) printf(" + %.12g*x^%d", beta[i],i);
    else           printf(" - %.12g*x^%d",-beta[i],i);
  }
  printf("\n");
  if (nobs==np) printf("exact fit!\n"), exit(0);
  var=sserr/(nobs-np);
  cov_(&np,&nrbar,d,rbar,&nreq,rinv,&var,covmat,&dimcov,sterr,&ier);
  if (ier!=0) fprintf(stderr,"cov ier=%d\n",ier), exit(1);
  printf("parameter standard errors: ");
  printf("% #.3g    ",sterr[0]);
  if (np>1) printf("% #.3g    ",sterr[1]);
  for (i=2; i<np; i++) printf(" % #.3g    ",sterr[i]);
  printf("\n");
  printf("parameter covariance matrix:\n");
  k=0;
  for (i=0; i<np; i++) {
    for (j=0; j<i; j++) printf("       ");
    for (j=0; j<np-i; j++) printf("% #.3g ",covmat[k++]);
    printf("\n");
  }
  return 0;
}

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=='\'') ? *line++ : '\0';
    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;
}
