// Copyright (C) 2004, 2010 International Business Machines and others.
// All Rights Reserved.
// This code is published under the Eclipse Public License.
//
// Authors:  Carl Laird, Andreas Waechter     IBM    2004-09-02

#include "IpStdInterfaceTNLP.hpp"
#include "IpBlas.hpp"

namespace Ipopt
{

StdInterfaceTNLP::StdInterfaceTNLP(
   Index           n_var,
   const Number*   x_L,
   const Number*   x_U,
   Index           n_con,
   const Number*   g_L,
   const Number*   g_U,
   Index           nele_jac,
   Index           nele_hess,
   Index           index_style,
   const Number*   start_x,
   const Number*   start_lam,
   const Number*   start_z_L,
   const Number*   start_z_U,
   Eval_F_CB       eval_f,
   Eval_G_CB       eval_g,
   Eval_Grad_F_CB  eval_grad_f,
   Eval_Jac_G_CB   eval_jac_g,
   Eval_H_CB       eval_h,
   Intermediate_CB intermediate_cb,
   Number*         x_sol,
   Number*         z_L_sol,
   Number*         z_U_sol,
   Number*         g_sol,
   Number*         lam_sol,
   Number*         obj_sol,
   UserDataPtr     user_data,
   Number          obj_scaling /*=1*/,
   const Number*   x_scaling /*= NULL*/,
   const Number*   g_scaling /*= NULL*/
)
   : TNLP(),
     n_var_(n_var),
     n_con_(n_con),
     x_L_(x_L),
     x_U_(x_U),
     g_L_(g_L),
     g_U_(g_U),
     nele_jac_(nele_jac),
     nele_hess_(nele_hess),
     index_style_(index_style),
     start_x_(start_x),
     start_lam_(start_lam),
     start_z_L_(start_z_L),
     start_z_U_(start_z_U),
     eval_f_(eval_f),
     eval_g_(eval_g),
     eval_grad_f_(eval_grad_f),
     eval_jac_g_(eval_jac_g),
     eval_h_(eval_h),
     intermediate_cb_(intermediate_cb),
     user_data_(user_data),
     obj_scaling_(obj_scaling),
     x_scaling_(NULL),
     g_scaling_(NULL),
     non_const_x_(NULL),
     x_sol_(x_sol),
     z_L_sol_(z_L_sol),
     z_U_sol_(z_U_sol),
     g_sol_(g_sol),
     lambda_sol_(lam_sol),
     obj_sol_(obj_sol),
     ip_data_(NULL),
     ip_cq_(NULL)
{
   ASSERT_EXCEPTION(n_var_ > 0, INVALID_STDINTERFACE_NLP, "The number of variables must be at least 1.");
   ASSERT_EXCEPTION(n_con_ >= 0, INVALID_STDINTERFACE_NLP, "The number of constrains must be non-negative.");
   ASSERT_EXCEPTION(x_L_, INVALID_STDINTERFACE_NLP, "No lower bounds for variables provided.");
   ASSERT_EXCEPTION(x_U_, INVALID_STDINTERFACE_NLP, "No upper bounds for variables provided.");
   ASSERT_EXCEPTION(g_L_ || n_con_ == 0, INVALID_STDINTERFACE_NLP, "No lower bounds for constraints provided.");
   ASSERT_EXCEPTION(g_U_ || n_con_ == 0, INVALID_STDINTERFACE_NLP, "No upper bounds for constraints provided.");
   ASSERT_EXCEPTION(nele_jac_ >= 0, INVALID_STDINTERFACE_NLP, "Number of non-zero elements in constraint Jacobian must be non-negative.");
   ASSERT_EXCEPTION(nele_hess_ >= 0, INVALID_STDINTERFACE_NLP, "Number of non-zero elements in Hessian of Lagrangian must be non-negative.");
   ASSERT_EXCEPTION(index_style_ == 0 || index_style_ == 1, INVALID_STDINTERFACE_NLP, "Valid index styles are 0 (C style) or 1 (Fortran style)");
   ASSERT_EXCEPTION(start_x_, INVALID_STDINTERFACE_NLP, "No initial point for the variables provided.");
   ASSERT_EXCEPTION(eval_f_, INVALID_STDINTERFACE_NLP, "No callback function for evaluating the value of objective function provided.");
   ASSERT_EXCEPTION(eval_g_, INVALID_STDINTERFACE_NLP, "No callback function for evaluating the values of constraints provided.");
   ASSERT_EXCEPTION(eval_grad_f_, INVALID_STDINTERFACE_NLP, "No callback function for evaluating the gradient of objective function provided.");
   ASSERT_EXCEPTION(eval_jac_g_, INVALID_STDINTERFACE_NLP, "No callback function for evaluating the Jacobian of the constraints provided.");
   ASSERT_EXCEPTION(eval_h_, INVALID_STDINTERFACE_NLP, "No callback function for evaluating the Hessian of the constraints provided.");

   if( x_scaling != NULL )
   {
      Number* tmp = new Number[n_var_];
      Ipopt::IpBlasCopy(n_var_, x_scaling, 1, tmp, 1);
      x_scaling_ = tmp;
   }
   if( g_scaling )
   {
      Number* tmp = new Number[n_con_];
      Ipopt::IpBlasCopy(n_con_, g_scaling, 1, tmp, 1);
      g_scaling_ = tmp;
   }
}

StdInterfaceTNLP::~StdInterfaceTNLP()
{
   delete[] non_const_x_;
   delete[] x_scaling_;
   delete[] g_scaling_;
}

bool StdInterfaceTNLP::get_nlp_info(
   Index&          n,
   Index&          m,
   Index&          nnz_jac_g,
   Index&          nnz_h_lag,
   IndexStyleEnum& index_style
)
{
   n = n_var_; // # of variables (variable types have been asserted in the constructor
   m = n_con_; // # of constraints
   nnz_jac_g = nele_jac_;  // # of non-zeros in the jacobian
   nnz_h_lag = nele_hess_; // # of non-zeros in the hessian

   index_style = (index_style_ == 0) ? C_STYLE : FORTRAN_STYLE;

   return true;
}

bool StdInterfaceTNLP::get_bounds_info(
   Index   n,
   Number* x_l,
   Number* x_u,
   Index   m,
   Number* g_l,
   Number* g_u
)
{
   DBG_ASSERT(n == n_var_);
   DBG_ASSERT(m == n_con_);

   Ipopt::IpBlasCopy(n, x_L_, 1, x_l, 1);
   Ipopt::IpBlasCopy(n, x_U_, 1, x_u, 1);

   Ipopt::IpBlasCopy(m, g_L_, 1, g_l, 1);
   Ipopt::IpBlasCopy(m, g_U_, 1, g_u, 1);

   return true;
}

bool StdInterfaceTNLP::get_scaling_parameters(
   Number& obj_scaling,
   bool&   use_x_scaling,
   Index   n,
   Number* x_scaling,
   bool&   use_g_scaling,
   Index   m,
   Number* g_scaling
)
{
   DBG_ASSERT(n == n_var_);
   (void) n;
   DBG_ASSERT(m == n_con_);
   (void) m;

   obj_scaling = obj_scaling_;
   if( x_scaling_ != NULL )
   {
      use_x_scaling = true;
      Ipopt::IpBlasCopy(n_var_, x_scaling_, 1, x_scaling, 1);
   }
   else
   {
      use_x_scaling = false;
   }

   if( g_scaling_ != NULL )
   {
      use_g_scaling = true;
      Ipopt::IpBlasCopy(n_con_, g_scaling_, 1, g_scaling, 1);
   }
   else
   {
      use_g_scaling = false;
   }

   return true;
}

bool StdInterfaceTNLP::get_starting_point(
   Index   n,
   bool    init_x,
   Number* x,
   bool    init_z,
   Number* z_L,
   Number* z_U,
   Index   m,
   bool    init_lambda,
   Number* lambda
)
{
   bool retval = true;

   DBG_ASSERT(n == n_var_);
   DBG_ASSERT(m == n_con_);

   if( init_x )
   {
      Ipopt::IpBlasCopy(n, start_x_, 1, x, 1);
   }

   if( init_z )
   {
      if( start_z_L_ == NULL )
      {
         retval = false;
      }
      else
      {
         Ipopt::IpBlasCopy(n, start_z_L_, 1, z_L, 1);
      }

      if( start_z_U_ == NULL )
      {
         retval = false;
      }
      else
      {
         Ipopt::IpBlasCopy(n, start_z_U_, 1, z_U, 1);
      }
   }

   if( init_lambda )
   {
      if( start_lam_ == NULL )
      {
         retval = false;
      }
      else
      {
         Ipopt::IpBlasCopy(m, start_lam_, 1, lambda, 1);
      }
   }

   return retval;
}

bool StdInterfaceTNLP::eval_f(
   Index         n,
   const Number* x,
   bool          new_x,
   Number&       obj_value
)
{
   DBG_ASSERT(n == n_var_);

   apply_new_x(new_x, n, x);

   Bool retval = (*eval_f_)(n, non_const_x_, (Bool) new_x, &obj_value, user_data_);

   return (retval != 0);
}

bool StdInterfaceTNLP::eval_grad_f(
   Index         n,
   const Number* x,
   bool          new_x,
   Number*       grad_f
)
{
   DBG_ASSERT(n == n_var_);

   apply_new_x(new_x, n, x);

   Bool retval = (*eval_grad_f_)(n, non_const_x_, (Bool) new_x, grad_f, user_data_);

   return (retval != 0);
}

bool StdInterfaceTNLP::eval_g(
   Index         n,
   const Number* x,
   bool          new_x,
   Index         m,
   Number*       g
)
{
   DBG_ASSERT(n == n_var_);
   DBG_ASSERT(m == n_con_);

   apply_new_x(new_x, n, x);

   Bool retval = (*eval_g_)(n, non_const_x_, (Bool) new_x, m, g, user_data_);

   return (retval != 0);
}

bool StdInterfaceTNLP::eval_jac_g(
   Index         n,
   const Number* x,
   bool          new_x,
   Index         m,
   Index         nele_jac,
   Index*        iRow,
   Index*        jCol,
   Number*       values
)
{
   DBG_ASSERT(n == n_var_);
   DBG_ASSERT(nele_jac == nele_jac_);
   // need valid combination of iRow, jCol, and values pointers
   DBG_ASSERT((iRow != NULL && jCol != NULL && values == NULL) || (iRow == NULL && jCol == NULL && values != NULL));

   apply_new_x(new_x, n, x);
   Bool retval = (*eval_jac_g_)(n, non_const_x_, (Bool) new_x, m, nele_jac, iRow, jCol, values, user_data_);

   return (retval != 0);
}

bool StdInterfaceTNLP::eval_h(
   Index         n,
   const Number* x,
   bool          new_x,
   Number        obj_factor,
   Index         m,
   const Number* lambda,
   bool          new_lambda,
   Index         nele_hess,
   Index*        iRow,
   Index*        jCol,
   Number*       values
)
{
   DBG_ASSERT(n == n_var_);
   DBG_ASSERT(m == n_con_);
   DBG_ASSERT(nele_hess == nele_hess_);
   DBG_ASSERT((iRow != NULL && jCol != NULL && values == NULL) || (iRow == NULL && jCol == NULL && values != NULL));

   apply_new_x(new_x, n, x);

   Number* non_const_lambda = new Number[m];
   if( lambda )
   {
      Ipopt::IpBlasCopy(m, lambda, 1, non_const_lambda, 1);
   }

   Bool retval = (*eval_h_)(n, non_const_x_, (Bool) new_x, obj_factor, m, non_const_lambda, (Bool) new_lambda, nele_hess, iRow, jCol, values, user_data_);

   delete[] non_const_lambda;

   return (retval != 0);
}

bool StdInterfaceTNLP::intermediate_callback(
   AlgorithmMode              mode,
   Index                      iter,
   Number                     obj_value,
   Number                     inf_pr,
   Number                     inf_du,
   Number                     mu,
   Number                     d_norm,
   Number                     regularization_size,
   Number                     alpha_du,
   Number                     alpha_pr,
   Index                      ls_trials,
   const IpoptData*           ip_data,
   IpoptCalculatedQuantities* ip_cq
)
{
   if( !intermediate_cb_ )
   {
      return true;
   }

   ip_data_ = ip_data;
   ip_cq_ = ip_cq;

   Bool retval = (*intermediate_cb_)((Index) mode, iter, obj_value, inf_pr, inf_du, mu, d_norm, regularization_size,
                                     alpha_du, alpha_pr, ls_trials, user_data_);

   // cppcheck-suppress redundantAssignment
   ip_data_ = NULL;
   // cppcheck-suppress redundantAssignment
   ip_cq_ = NULL;

   return (retval != 0);
}

void StdInterfaceTNLP::finalize_solution(
   SolverReturn               /*status*/,
   Index                      n,
   const Number*              x,
   const Number*              z_L,
   const Number*              z_U,
   Index                      m,
   const Number*              g,
   const Number*              lambda,
   Number                     obj_value,
   const IpoptData*           /*ip_data*/,
   IpoptCalculatedQuantities* /*ip_cq*/
)
{
   if( x_sol_ != NULL )
   {
      IpBlasCopy(n, x, 1, x_sol_, 1);
   }

   if( z_L_sol_ != NULL )
   {
      IpBlasCopy(n, z_L, 1, z_L_sol_, 1);
   }

   if( z_U_sol_ != NULL )
   {
      IpBlasCopy(n, z_U, 1, z_U_sol_, 1);
   }

   if( g_sol_ != NULL )
   {
      IpBlasCopy(m, g, 1, g_sol_, 1);
   }

   if( lambda_sol_ != NULL )
   {
      IpBlasCopy(m, lambda, 1, lambda_sol_, 1);
   }

   if( obj_sol_ != NULL )
   {
      *obj_sol_ = obj_value;
   }

   // don't need to store the status, we get the status from the OptimizeTNLP method
}

void StdInterfaceTNLP::apply_new_x(
   bool  new_x,
   Index n,
   const Number* x
)
{
   if( new_x )
   {
      DBG_ASSERT(x != NULL);

      //copy the data to the non_const_x_
      if( non_const_x_ == NULL )
      {
         non_const_x_ = new Number[n];
      }

      Ipopt::IpBlasCopy(n, x, 1, non_const_x_, 1);
   }
}

} // namespace Ipopt
