// ad.h    KMB Last revised 1996 Feb 19, hacked for egcs 98 Sep 14
// for gcc 2.7.1
// Version 1.9
// automatic differentiation package
// global dim sets dimension of `phase space'
// initialization:  Ad u(x,n) => u=x, indpt var number n.
// Beware increasing dim without redeclaring Ads.
// TO DO:  Operator=(const float) should not set grad=0 ???
// Change log:
// 95Nov24 Ad(const& double) instead of Ad(double,int)
// 95Nov29 Ad& Ad::operator+=(Ad)
// 95Dec01 Ad& Ad::operator*=(Ad)

#ifndef __AD_H__
#define __AD_H__
#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <cmath>

extern int dim;
double dnorm(double x){return fabs(x);}
double sqr(double x){return x*x;}
int min(int x,int y){return x<y?x:y;}

class Ad {
  double* grad; // grad[0] is value.
  int size;
public:
  //static int dim;
  Ad();                            // constructor
  Ad(double);                      // constructor
  //Ad(const double&);               // constructor
  ~Ad();                           // destructor
  Ad(const Ad&);                   // copy
  //Ad& operator=(double);
  Ad& operator=(const double);     // assign
  Ad& operator=(const Ad&);        // assign
  void is_iv(int);
//  operator double() {return grad[0];};
  double& operator[](int) const;
  void dump(char *); // dump Ad to cout, with gradients
  friend Ad operator+(const Ad&, const Ad&);
  friend Ad operator+(double, const Ad&);
  friend Ad operator+(const Ad&, double);
  Ad& operator+=(const Ad&);
  Ad& operator*=(const Ad&);
  friend Ad operator-(const Ad&);  // unary -
  friend Ad operator-(const Ad&, const Ad&);
  friend Ad operator-(double, const Ad&);
  friend Ad operator-(const Ad&, double);
  friend Ad operator*(const Ad&, const Ad&);
  //friend Ad operator*(const double, const Ad&);
  friend Ad operator*(int, const Ad&); 
  friend Ad operator*(double, const Ad&); 
  friend Ad operator/(const Ad&, const Ad&);
  friend Ad operator/(const double, const Ad&);
  friend Ad operator/(const Ad&, double);
  friend Ad operator/(const Ad&, int);
  friend int operator< (const Ad&, const double&); 
  friend int operator< (const Ad&, const Ad&);
  friend int operator> (const Ad&, const Ad&); 
  friend int operator> (const Ad&, const double&); 
  friend int operator>=(const Ad&, const Ad&);
  friend int operator<=(const Ad&, const Ad&);
  friend int operator<=(const Ad&, const double&);
  friend int operator==(const Ad&, const Ad&);
  friend int operator==(const Ad&, const double&);
  friend int operator!=(const Ad&, const Ad&);
  friend double fabs(const Ad&);
  friend double dnorm(const Ad&);
  friend Ad sqr(const Ad&);  // square
  friend Ad sqrt(const Ad&);  // square root
  friend Ad exp(const Ad&);
  friend Ad log(const Ad&);
  friend Ad powint(const Ad&, int);
  friend Ad pow(const Ad&, const double&);
  friend Ad recip(const Ad&);
  friend Ad sin(const Ad&);  // Add later
  friend Ad cos(const Ad&);
  friend int siz(const Ad& u){ return u.size; }
//  friend ostream& operator << (ostream&, const Ad&);
}; // end class Ad

/// definitions start here...

void Ad::is_iv(int k){ 
  for (int i=1; i<=size; i++) grad[i] = double(0.0); 
  if (k>0 && k<=size) grad[k] = double(1.0);
  else cerr<<"Ad::is_iv called, k="<<k<<", size="<<size<<endl;
}

Ad::Ad(const Ad& u){ // copy ctor
  int i;
  //size = u.size;
  //for (i=0; i<=size; i++) grad[i] = u.grad[i];
  size = dim;
  grad = new double[size+1];
  for (i=0; i<=min(size,u.size); i++) grad[i] = u.grad[i];
  for (i=min(size,u.size)+1; i<=size; i++) grad[i] = double(0.0);
}

Ad::Ad(){ // constructor
  grad = new double[dim+1]; 
  size = dim;
  //cerr << "Ad constructed, dim="<<dim<<endl;
}

Ad::Ad(double x){ // constructor
  grad = new double[dim+1]; 
  size = dim;
  grad[0] = double(x);
  for (int i=1; i<=dim; i++) grad[i] = double(0.0);
}


//Ad::Ad(const double& x){ // constructor with init
//  grad = new double[dim+1]; 
//  size = dim;
//  grad[0] = x;
//  for (int i=1; i<=dim; i++) grad[i] = double(0.0);
//}

Ad::~Ad() { delete [] grad; };  // dtor

double& Ad::operator[](int i) const {
  if (i<0 || i>size) {
    cerr << "Ad operator[]: index="<<i<<", size="<<size<<endl;
    cerr << "Ad operator[]: quitting." << endl;
    exit(1);
  }
  return grad[i];
}

void Ad::dump(char* lab=""){
    cout << lab;  
    cout << "<" << grad[0] << ";";
    for (int i=1; i<dim; i++) cout << " " << grad[i] << ","; 
    cout << " " << grad[dim]; 
    cout  << ">\n"; 
}

ostream& operator << (ostream& s, const Ad& x){
  // should set width etc. here...
  // s<<setprecision(6);
  s << "<" << x[0] << ";";
  int n=siz(x);
  for (int i=1; i<n; i++) s << " " << x[i] << ","; 
  if (n>1) s << " " << x[n]; 
  s  << "> "; 
  return s;
}

int operator< (const Ad& x, const double& y) { return x[0]< y; }

int operator> (const Ad& x, const Ad& y) { return x[0]> y[0]; }
int operator> (const Ad& x, const double& y) { return x[0]>y; }
int operator< (const Ad& x, const Ad& y) { return x[0]< y[0]; }
int operator>=(const Ad& x, const Ad& y) { return x[0]>=y[0]; }
int operator<=(const Ad& x, const Ad& y) { return x[0]<=y[0]; }
int operator<=(const Ad& x, const double& y) { return x[0]<=y; }
int operator==(const Ad& x, const Ad& y) { return x[0]==y[0]; }
int operator==(const Ad& x, const double& y) { return x[0]==y; }
int operator!=(const Ad& x, const Ad& y) { return x[0]!=y[0]; }

/*
Ad& Ad::operator=(double u){ // assign from double
  grad[0] = double(u);
  for (int i=1; i<=size; i++) grad[i] = 0.0;
  return *this;
}
*/
Ad& Ad::operator=(const double u){ // assign
  //if (!grad) grad=new double[dim+1];
  grad[0] = u;
  for (int i=1; i<=size; i++) grad[i] = double(0.0);
  return *this;
}

Ad& Ad::operator=(const Ad& u){ // assign
  int i;
  //if (size>u.size) {
  //   cout<<"Warning: size error in Ad operator=(Ad)\n";
  //   cout << "   Left size=" << size << endl;
  //   cout << "  Right size=" << u.size << endl;
  //   exit(1);
  //}
  for (i=0; i<=min(size,u.size); i++) grad[i] = u.grad[i];
  for (i=min(size,u.size)+1; i<=size; i++) grad[i] = double(0.0);
  return *this;
}

Ad operator+(const Ad& u, const Ad& v){
  Ad w;
  for (int i=0; i<=dim; i++) w.grad[i] = u.grad[i]+v.grad[i];
  return w;
}

Ad operator+(const Ad& v, double c){
  Ad w;
  w.grad[0] = c + v.grad[0];
  for (int i=1; i<=dim; i++) w.grad[i] = v.grad[i];
  return w;
}

Ad operator+(double c, const Ad& v){
  Ad w;
  w.grad[0] = c + v.grad[0];
  for (int i=1; i<=dim; i++) w.grad[i] = v.grad[i];
  return w;
}

Ad& Ad::operator+=(const Ad& u){
  for (int i=0; i<=dim; i++) grad[i] += u.grad[i];
  return *this;
}

Ad& Ad::operator*=(const Ad& v){
  double g0=grad[0], vg0=v.grad[0];
  for (int i=1; i<=dim; i++) grad[i] = grad[i]*vg0+g0*v.grad[i];
  grad[0] *= vg0;
  return *this;
}

Ad operator-(const Ad& u, const Ad& v){
  Ad w;
  for (int i=0; i<=dim; i++) w.grad[i] = u.grad[i]-v.grad[i];
  return w;
}

Ad operator-(const Ad& v, double c){
  Ad w;
  w.grad[0] = v.grad[0]-c;
  for (int i=1; i<=dim; i++) w.grad[i] = v.grad[i];
  return w;
}

Ad operator-(double c, const Ad& v){
  Ad w;
  w.grad[0] = c - v.grad[0];
  for (int i=1; i<=dim; i++) w.grad[i] = -v.grad[i];
  return w;
}

Ad operator-(const Ad& u){ // unary -
  Ad w;
  for (int i=0; i<=dim; i++) w.grad[i] = -u.grad[i];
  return w;
}

Ad operator*(const Ad& u, const Ad& v){
  Ad w; int i;
  w.grad[0] = u.grad[0] * v.grad[0];
  for (i=1; i<=dim; i++) w.grad[i] = u.grad[i]*v.grad[0]+u.grad[0]*v.grad[i];
  return w;
}

Ad operator/(const Ad& u, const Ad& v){
  Ad w; int i; double v2;
  w.grad[0] = u.grad[0]/v.grad[0];
  if (dim>0) v2 = u.grad[0]/(v.grad[0]*v.grad[0]);
  for (i=1; i<=dim; i++) w.grad[i]=u.grad[i]/v.grad[0]-v.grad[i]*v2;
  return w;
}

Ad operator*(int c, const Ad& v){
  Ad w;
  for (int i=0; i<=dim; i++) w.grad[i] = c*v.grad[i];
  return w;
}

Ad operator*(double c, const Ad& v){
  Ad w;
  for (int i=0; i<=dim; i++) w.grad[i] = double(c)*v.grad[i];
  return w;
}

/*
Ad operator*(const double c, const Ad& v){
  Ad w;
  for (int i=0; i<=dim; i++) w.grad[i] = c*v.grad[i];
  return w;
}
*/

Ad operator/(const double c, const Ad& v){
  Ad w;
  w.grad[0] = c/v.grad[0];
  double a = -w.grad[0]/v.grad[0];
  for (int i=1; i<=dim; i++) w.grad[i] = a*v.grad[i];
  return w;
}

Ad operator/(const Ad& v, double c){
  Ad w; for (int i=0; i<=dim; i++) w.grad[i] = v.grad[i]/c; return w;
}

Ad operator/(const Ad& v, int c){
  Ad w; for (int i=0; i<=dim; i++) w.grad[i] = v.grad[i]/c; return w;
}

double fabs(const Ad& v){
  Ad w; int i;
  return fabs(v.grad[0]);
/*
  w.grad[0] = fabs(v.grad[0]);
  if (v.grad[0]>=0.0){ // should really bomb on v.value==0 !
    for (i=1; i<=dim; i++) w.grad[i] =  v.grad[i];
  } else {
    for (i=1; i<=dim; i++) w.grad[i] = -v.grad[i];
  };
  return w;
*/
}

double dnorm(const Ad& v){ return dnorm(v.grad[0]); }

Ad exp(const Ad& v){
  Ad w;
  w.grad[0] = exp(v[0]);
  for (int i=1; i<=dim; i++) w.grad[i] = w.grad[0]*v.grad[i];
  return w;
}

Ad log(const Ad& v){
  Ad w;
  w.grad[0] = log(v.grad[0]);
  for (int i=1; i<=dim; i++) w.grad[i] = v.grad[i]/v.grad[0];
  return w;
}

Ad sin(const Ad& v){
  Ad w;
  w.grad[0] = sin(v.grad[0]);
  double c = cos(v.grad[0]);
  for (int i=1; i<=dim; i++) w.grad[i] = c*v.grad[i];
  return w;
}

Ad cos(const Ad& v){
  Ad w;
  w.grad[0] = cos(v.grad[0]);
  double s = -sin(v.grad[0]);
  for (int i=1; i<=dim; i++) w.grad[i] = s*v.grad[i];
  return w;
}

Ad sqr(const Ad& u){
  Ad w; 
  w.grad[0] = u.grad[0] * u.grad[0];
  double c = u.grad[0]+u.grad[0];
  for (int i=1; i<=dim; i++) w.grad[i] = c*u.grad[i];
  return w;
}

Ad sqrt(const Ad& u){
  Ad w; int i;
  w.grad[0] = sqrt(u.grad[0]);
  double c = double(0.5)/w.grad[0];
  for (i=1; i<=dim; i++) w.grad[i] = c*u.grad[i];
  return w;
}

Ad powint(const Ad& u, int c){
  Ad w; int i;  
  switch (c) {
    case -2: { w=recip(sqr(u)); return w; } 
    case -1: { w=recip(u); return w; } 
    case 0: return Ad(1.0);  // O^0 = NaN ??
    case 1: return u;  
    case 2: return u*u;
    case 3: return u*sqr(u);
    case 4: return sqr(sqr(u));
    default: { // binary method
      int n=c, m; Ad y=1.0, z=u; if (n<0) n=-n;
      do {
        m=n; n=n/2;
        if (n+n!=m) {// m odd
           y = z*y; 
           if (n==0) { if (c>0) return y; else return recip(y); }
        }
        z = sqr(z); 
      } while (1);
    }
  }
}

Ad pow(const Ad& u, const double& c){
  Ad w; int i;  double cc; cc=pow(u[0],c-double(1.0));
  w[0] = cc*u[0];
  cc = cc*c;
  for (i=1; i<=dim; i++) w[i] = cc*u[i];
  return w;
}

Ad recip(const Ad& v){
  Ad w;
  w.grad[0] = 1.0/v.grad[0];
  double c = -sqr(w.grad[0]);
  for (int i=1; i<=dim; i++) w.grad[i] = c*v.grad[i];
  return w;
}

#endif
