//
// $Source: /cvsroot/gambit/gambit/sources/tools/logit/nfglogit.cc,v $
// $Date: 2006/12/15 19:29:38 $
// $Revision: 1.28 $
//
// DESCRIPTION:
// Computation of quantal response equilibrium correspondence for
// normal form games.
//
// This file is part of Gambit
// Copyright (c) 2002, The Gambit Project
//
// 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 <unistd.h>
#include <math.h>
#include <iostream>
#include <fstream>
#include <libgambit/libgambit.h>
#include <libgambit/sqmatrix.h>

using namespace Gambit;


//=========================================================================
//             QRE Correspondence Computation via Homotopy
//=========================================================================

//
// The following code implements a homotopy approach to computing
// the logistic QRE correspondence.  This implementation is a basic
// Euler-Newton approach with adaptive step size, based upon the
// ideas and codes presented in Allgower and Georg's
// _Numerical Continuation Methods_.
//

inline double sqr(double x) { return x*x; }

static void Givens(Matrix<double> &b, Matrix<double> &q,
		   double &c1, double &c2, int l1, int l2, int l3)
{
  if (fabs(c1) + fabs(c2) == 0.0) {
    return;
  }

  double sn;
  if (fabs(c2) >= fabs(c1)) {
    sn = sqrt(1.0 + sqr(c1/c2)) * fabs(c2);
  }
  else {
    sn = sqrt(1.0 + sqr(c2/c1)) * fabs(c1);
  }
  double s1 = c1/sn;
  double s2 = c2/sn;

  for (int k = 1; k <= q.NumColumns(); k++) {
    double sv1 = q(l1, k);
    double sv2 = q(l2, k);
    q(l1, k) = s1 * sv1 + s2 * sv2;
    q(l2, k) = -s2 * sv1 + s1 * sv2;
  }

  for (int k = l3; k <= b.NumColumns(); k++) {
    double sv1 = b(l1, k);
    double sv2 = b(l2, k);
    b(l1, k) = s1 * sv1 + s2 * sv2;
    b(l2, k) = -s2 * sv1 + s1 * sv2;
  }

  c1 = sn;
  c2 = 0.0;
}

static void QRDecomp(Matrix<double> &b, Matrix<double> &q)
{
  q.MakeIdent();
  for (int m = 1; m <= b.NumColumns(); m++) {
    for (int k = m + 1; k <= b.NumRows(); k++) {
      Givens(b, q, b(m, m), b(k, m), m, k, m + 1);
    }
  }
}

static void NewtonStep(Matrix<double> &q, Matrix<double> &b,
		       Vector<double> &u, Vector<double> &y,
		       double &d)
{
  for (int k = 1; k <= b.NumColumns(); k++) {
    for (int l = 1; l <= k - 1; l++) {
      y[k] -= b(l, k) * y[l];
    }
    y[k] /= b(k, k);
  }

  d = 0.0;
  for (int k = 1; k <= b.NumRows(); k++) {
    double s = 0.0;
    for (int l = 1; l <= b.NumColumns(); l++) {
      s += q(l, k) * y[l];
    }
    u[k] -= s;
    d += s * s;
  }
  d = sqrt(d);
}


void QreLHS(const StrategySupport &p_support, 
	    const Vector<double> &p_point,
	    Vector<double> &p_lhs)
{
  MixedStrategyProfile<double> profile(p_support), logprofile(p_support);
  for (int i = 1; i <= profile.Length(); i++) {
    profile[i] = exp(p_point[i]);
    logprofile[i] = p_point[i];
  }
  double lambda = p_point[p_point.Length()];
  
  p_lhs = 0.0;

  int rowno = 0;
  for (int pl = 1; pl <= p_support.GetGame()->NumPlayers(); pl++) {
    GamePlayer player = p_support.GetGame()->GetPlayer(pl);
    for (int st = 1; st <= player->NumStrategies(); st++) {
      rowno++;
      if (st == 1) {
	// should be st==lead: sum-to-one equation
	p_lhs[rowno] = -1.0;
	for (int j = 1; j <= player->NumStrategies(); j++) {
	  p_lhs[rowno] += profile[player->GetStrategy(j)];
	}
      }
      else {
	p_lhs[rowno] = (logprofile[player->GetStrategy(st)] - 
			logprofile[player->GetStrategy(1)] -
			lambda * (profile.GetStrategyValue(player->GetStrategy(st)) -
				  profile.GetStrategyValue(player->GetStrategy(1))));

      }
    }
  }
}

void QreJacobian(const StrategySupport &p_support,
		 const Vector<double> &p_point,
		 Matrix<double> &p_matrix)
{
  MixedStrategyProfile<double> profile(p_support), logprofile(p_support);
  for (int i = 1; i <= profile.Length(); i++) {
    profile[i] = exp(p_point[i]);
    logprofile[i] = p_point[i];
  }
  double lambda = p_point[p_point.Length()];

  p_matrix = 0.0;

  int rowno = 0;
  for (int i = 1; i <= p_support.GetGame()->NumPlayers(); i++) {
    GamePlayer player = p_support.GetGame()->GetPlayer(i);

    for (int j = 1; j <= player->NumStrategies(); j++) {
      rowno++;
      if (j == 1) {
	// Should be j == lead: sum-to-one equation
	
	int colno = 0;
	for (int ell = 1; ell <= p_support.GetGame()->NumPlayers(); ell++) {
	  GamePlayer player2 = p_support.GetGame()->GetPlayer(ell);

	  for (int m = 1; m <= player2->NumStrategies(); m++) {
	    colno++;
	    
	    if (i == ell) {
	      p_matrix(colno, rowno) = profile[player2->GetStrategy(m)];
	    }
	    else {
	      p_matrix(colno, rowno) = 0.0;
	    }
	  }
	}
	
	// Derivative wrt lambda is zero
	p_matrix(p_matrix.NumRows(), rowno) = 0.0;
      }
      else {
	// This is a ratio equation

	int colno = 0;
	for (int ell = 1; ell <= p_support.GetGame()->NumPlayers(); ell++) {
	  GamePlayer player2 = p_support.GetGame()->GetPlayer(ell);

	  for (int m = 1; m <= player2->NumStrategies(); m++) {
	    colno++;

	    if (i == ell) {
	      if (m == 1) {
		// should be m==lead
		p_matrix(colno, rowno) = -1.0;
	      }
	      else if (m == j) {
		p_matrix(colno, rowno) = 1.0;
	      }
	      else {
		p_matrix(colno, rowno) = 0.0;
	      }
	    }
	    else {
	      // 1 == lead
	      p_matrix(colno, rowno) =
		-lambda * profile[player2->GetStrategy(m)] *
		(profile.GetPayoffDeriv(i, 
					p_support.GetStrategy(i, j),
					p_support.GetStrategy(ell, m)) -
		 profile.GetPayoffDeriv(i, 
					p_support.GetStrategy(i, 1),
					p_support.GetStrategy(ell, m)));
	    }
	  }

	}
	
	// column wrt lambda
	// 1 == lead
	p_matrix(p_matrix.NumRows(), rowno) =
	  (profile.GetStrategyValue(p_support.GetStrategy(i, 1)) - 
	   profile.GetStrategyValue(p_support.GetStrategy(i, j)));
      }
    }
  }

  /*
  std::cout << "Jac:\n";
  for (int i = 1; i <= p_matrix.NumColumns(); i++) {
    for (int j = 1; j <= p_matrix.NumRows(); j++) {
      std::cout << p_matrix(j, i) << " ";
    }
    std::cout << std::endl;
  }
  */
}

//
// For maximum likelihood estimation
//
extern bool g_maxLike;
extern Array<double> g_obsProbs;

extern double g_targetLambda;

double LogLike(const Array<double> &p_point)
{
  double ret = 0.0;
  
  for (int i = 1; i <= g_obsProbs.Length(); i++) {
    ret += g_obsProbs[i] * log(p_point[i]);
  }

  return ret;
}

double DiffLogLike(const Array<double> &p_point,
		   const Array<double> &p_tangent)
{
  double ret = 0.0;

  for (int i = 1; i <= g_obsProbs.Length(); i++) {
    ret += g_obsProbs[i] * p_tangent[i];
  }

  return ret;
}


extern int g_numDecimals;

void PrintProfile(std::ostream &p_stream,
		  const StrategySupport &p_support, const Vector<double> &x,
		  bool p_terminal = false)
{
  p_stream.setf(std::ios::fixed);
  // By convention, we output lambda first
  if (!p_terminal) {
    p_stream << std::setprecision(g_numDecimals) << x[x.Length()];
  }
  else {
    p_stream << "NE";
  }
  p_stream.unsetf(std::ios::fixed);

  for (int i = 1; i <  x.Length(); i++) {
    p_stream << "," << std::setprecision(g_numDecimals) << exp(x[i]);
  }

  if (g_maxLike) {
    MixedStrategyProfile<double> profile(p_support);
    for (int i = 1; i <= profile.Length(); i++) {
      profile[i] = exp(x[i]);
    }

    p_stream.setf(std::ios::fixed);
    p_stream << "," << std::setprecision(g_numDecimals) << LogLike(profile);
    p_stream.unsetf(std::ios::fixed);
  }

  p_stream << std::endl;
}

//
// TracePath does the real work of tracing a branch of the correspondence
//
// Strategy:
// This is the standard simple PC continuation method outlined in
// Allgower & Georg, _Numerical Continuation Methods_.
//
// The only novelty is the handling of the representation of the
// probabilities.  We deal with a correspondence in which probabilities
// often tend to zero exponentially in the lambda parameter.  However,
// negative probabilities make no sense, and cause the defining equations
// to be ill-defined.  This suggests that representing the probabilities
// as logarithms is indicated.  However, experience is that this does
// not work well when lambda is small, as the change in the probabilities
// as lambda changes in this region is closer to linear than exponential.
//
// The compromise is this: we represent probabilities below a certain
// cutoff (here set to .05) as logarithms, and probabilities above that
// as the actual probability.  Thus, we can take advantage of the
// exponential decay of small probabilities.
//


extern double g_maxDecel;
extern double g_hStart;
extern bool g_fullGraph;

void 
TraceStrategicPath(const MixedStrategyProfile<double> &p_start,
		   double p_startLambda, double p_maxLambda, double p_omega)
{
  const double c_tol = 1.0e-4;     // tolerance for corrector iteration
  const double c_maxDist = 0.4;    // maximal distance to curve
  const double c_maxContr = 0.6;   // maximal contraction rate in corrector
  const double c_eta = 0.1;        // perturbation to avoid cancellation
                                   // in calculating contraction rate
  double h = g_hStart;             // initial stepsize
  const double c_hmin = 1.0e-8;    // minimal stepsize

  bool newton = false;          // using Newton steplength (for MLE)
  bool restarting = false;      // flag for first restart step after MLE

  // When doing MLE finding, we push the data from the original path-following
  // here, and resume once we've found the local extremum.
  Vector<double> pushX(p_start.Length() + 1);
  double pushH = h;

  Vector<double> x(p_start.Length() + 1), u(p_start.Length() + 1);
  for (int i = 1; i <= p_start.Length(); i++) {
    x[i] = log(p_start[i]);
  }
  x[x.Length()] = p_startLambda;

  if (g_fullGraph) {
    PrintProfile(std::cout, p_start.GetSupport(), x);
  }

  Vector<double> t(p_start.Length() + 1);
  Vector<double> y(p_start.Length());

  Matrix<double> b(p_start.Length() + 1, p_start.Length());
  SquareMatrix<double> q(p_start.Length() + 1);
  QreJacobian(p_start.GetSupport(), x, b);
  QRDecomp(b, q);
  q.GetRow(q.NumRows(), t);
  
  while (x[x.Length()] >= 0.0 && x[x.Length()] < p_maxLambda) {
    bool accept = true;

    if (fabs(h) <= c_hmin) {
      // Stop.  If this occurs because we are in MLE-finding mode,
      // resume tracing the original branch
      if (newton && g_maxLike) {
	//printf("popping! %f\n", pushH);
	x = pushX;
	h = pushH;
	QreJacobian(p_start.GetSupport(), x, b);
	QRDecomp(b, q);
	q.GetRow(q.NumRows(), t);
	newton = false;
	restarting = true;
       	continue;
      }
      else {
	// We're really done.
	return;
      }
    }

    // Predictor step
    for (int k = 1; k <= x.Length(); k++) {
      u[k] = x[k] + h * p_omega * t[k];
    }

    double decel = 1.0 / g_maxDecel;  // initialize deceleration factor
    QreJacobian(p_start.GetSupport(), u, b);
    QRDecomp(b, q);

    int iter = 1;
    double disto = 0.0;
    while (true) {
      double dist;

      QreLHS(p_start.GetSupport(), u, y);
      NewtonStep(q, b, u, y, dist); 

      if (dist >= c_maxDist) {
	accept = false;
	break;
      }
      
      decel = max(decel, sqrt(dist / c_maxDist) * g_maxDecel);
      if (iter >= 2) {
	double contr = dist / (disto + c_tol * c_eta);
	if (contr > c_maxContr) {
	  accept = false;
	  break;
	}
	decel = max(decel, sqrt(contr / c_maxContr) * g_maxDecel);
      }

      if (dist <= c_tol) {
	// Success; break out of iteration
	break;
      }
      disto = dist;
      iter++;
    }

    if (!accept) {
      h /= g_maxDecel;   // PC not accepted; change stepsize and retry
      if (fabs(h) <= c_hmin) {
	// Stop.  If this occurs because we are in MLE-finding mode,
	// resume tracing the original branch
	if (newton && g_maxLike) {
	  //printf("popping! %f\n", pushH);
	  x = pushX;
	  h = pushH;
	  newton = false;
	  restarting = true;
	  QreJacobian(p_start.GetSupport(), x, b);
	  QRDecomp(b, q);
	  q.GetRow(q.NumRows(), t);
	  continue;
	}
	else {
	  // We're really done.
	  return;
	}
      }

      continue;
    }

    // Determine new stepsize
    if (decel > g_maxDecel) {
      decel = g_maxDecel;
    }

    if (!newton && g_maxLike) {
      // Currently, 't' is the tangent at 'x'.  We also need the
      // tangent at 'u'.
      Vector<double> newT(t);
      q.GetRow(q.NumRows(), newT); 

      if (!restarting && 
	  DiffLogLike(x, t) * DiffLogLike(u, newT) < 0.0) {
	// Store the current state, to resume later
	pushX = x;
	pushH = h;
	newton = true;
	//printf("entering newton mode\n");
      }
    }
    else if (!newton && g_targetLambda > 0.0) {
      if (!restarting &&
	  ((x[x.Length()] - g_targetLambda) *
	   (u[u.Length()] - g_targetLambda)) < 0.0) {
	// Store the current state, to resume later
	pushX = x;
	pushH = h;
	newton = true;
	//printf("entering newton mode\n");
      }
    }

    if (newton && g_maxLike) {
      // Newton-type steplength adaptation, secant method
      Vector<double> newT(t);
      q.GetRow(q.NumRows(), newT); 

      h *= -DiffLogLike(u, newT) / (DiffLogLike(u, newT) - 
				    DiffLogLike(x, t));
    }
    else if (newton && g_targetLambda > 0.0) {
      h *= -(u[u.Length()] - g_targetLambda) / (u[u.Length()] - x[x.Length()]);
    }
    else {
      // Standard steplength adaptation
      h = fabs(h / decel);
    }

    restarting = false;

    // PC step was successful; update and iterate
    x = u;

    if (g_fullGraph) {
      PrintProfile(std::cout, p_start.GetSupport(), x);
    }
    

    Vector<double> newT(t);
    q.GetRow(q.NumRows(), newT);  // new tangent
    if (t * newT < 0.0) {
      printf("Bifurcation! at %f\n", x[x.Length()]);
      // Bifurcation detected; for now, just "jump over" and continue,
      // taking into account the change in orientation of the curve.
      // Someday, we need to do more here! :)
      p_omega = -p_omega;
    }
    t = newT;

  }

  if (!g_fullGraph) {
    PrintProfile(std::cout, p_start.GetSupport(), x, true);
  }
}





syntax highlighted by Code2HTML, v. 0.9.1