// @(#)root/minuit2:$Id$
// Authors: M. Winkler, F. James, L. Moneta, A. Zsenei   2003-2005

/**********************************************************************
 *                                                                    *
 * Copyright (c) 2005 LCG ROOT Math team,  CERN/PH-SFT                *
 *                                                                    *
 **********************************************************************/

#include "Minuit2/FumiliGradientCalculator.h"
#include "Minuit2/FumiliFCNBase.h"
#include "Minuit2/MnUserTransformation.h"
#include "Minuit2/FunctionGradient.h"
#include "Minuit2/MinimumParameters.h"
#include "Minuit2/FumiliChi2FCN.h"
#include "Minuit2/FumiliMaximumLikelihoodFCN.h"

//to compare with N2P calculator
//#define DEBUG 1
#ifdef DEBUG
#include "Minuit2/MnPrint.h"
#include "Minuit2/Numerical2PGradientCalculator.h"
#include "Minuit2/MnStrategy.h"
#include "Minuit2/MnUserFcn.h"
#endif

namespace ROOT {

   namespace Minuit2 {


FunctionGradient FumiliGradientCalculator::operator()(const MinimumParameters& par) const {

   // Calculate gradient for Fumili using the gradient and Hessian provided by the FCN Fumili function
   // applying the external to int trasformation.

   int nvar = par.Vec().size();
   std::vector<double> extParam = fTransformation(  par.Vec() );
   //   std::vector<double> deriv;
   //   deriv.reserve( nvar );
   //   for (int i = 0; i < nvar; ++i) {
   //     unsigned int ext = fTransformation.ExtOfInt(i);
   //     if ( fTransformation.Parameter(ext).HasLimits())
   //       deriv.push_back( fTransformation.DInt2Ext( i, par.Vec()(i) ) );
   //     else
   //       deriv.push_back(1.0);
   //   }

   // eval Gradient
   FumiliFCNBase & fcn = const_cast<FumiliFCNBase &>(fFcn);

   fcn.EvaluateAll(extParam);


   MnAlgebraicVector v(nvar);
   MnAlgebraicSymMatrix h(nvar);


   const std::vector<double> & fcn_gradient = fFcn.Gradient();
   assert( fcn_gradient.size() == extParam.size() );


   //   for (int i = 0; i < nvar; ++i) {
   //     unsigned int iext = fTransformation.ExtOfInt(i);
   //     double ideriv = 1.0;
   //     if ( fTransformation.Parameter(iext).HasLimits())
   //       ideriv =  fTransformation.DInt2Ext( i, par.Vec()(i) ) ;


   //     //     v(i) = fcn_gradient[iext]*deriv;
   //     v(i) = ideriv*fcn_gradient[iext];

   //     for (int j = i; j < nvar; ++j) {
   //       unsigned int jext = fTransformation.ExtOfInt(j);
   //       double jderiv = 1.0;
   //       if ( fTransformation.Parameter(jext).HasLimits())
   // jderiv =  fTransformation.DInt2Ext( j, par.Vec()(j) ) ;

   // //       h(i,j) = deriv[i]*deriv[j]*fFcn.Hessian(iext,jext);
   //       h(i,j) = ideriv*jderiv*fFcn.Hessian(iext,jext);
   //     }
   //   }


   // cache deriv and Index values .
   // in large Parameter limit then need to re-optimize and see if better not caching

   std::vector<double> deriv(nvar);
   std::vector<unsigned int> extIndex(nvar);
   for (int i = 0; i < nvar; ++i) {
      extIndex[i] = fTransformation.ExtOfInt(i);
      deriv[i] = 1;
      if ( fTransformation.Parameter(extIndex[i]).HasLimits())
         deriv[i] =  fTransformation.DInt2Ext( i, par.Vec()(i) ) ;

      v(i) = fcn_gradient[extIndex[i]]*deriv[i];

      for (int j = 0; j <= i; ++j) {
         h(i,j) = deriv[i]*deriv[j]*fFcn.Hessian(extIndex[i],extIndex[j]);
      }
   }

#ifdef DEBUG
   // compare with other Gradient
   //   // calculate Gradient using Minuit method

   Numerical2PGradientCalculator gc(MnUserFcn(fFcn,fTransformation), fTransformation, MnStrategy(1));
   FunctionGradient g2 = gc(par);

   std::cout << "Fumili Gradient " << v << std::endl;
   std::cout << "Minuit Gradient " << g2.Vec() << std::endl;
#endif

   // store calculated Hessian
   fHessian = h;
   return FunctionGradient(v);
}

FunctionGradient FumiliGradientCalculator::operator()(const MinimumParameters& par,
                                                      const FunctionGradient&) const

{
   // Needed for interface of base class.
   return this->operator()(par);

}

   }  // namespace Minuit2

}  // namespace ROOT
