//==================================================================================================
// L i n e a r _ s y s t e m                                                              Interface
// G l p k
//                                                                                By Bruno Bachelet
//==================================================================================================
// Copyright (c) 1999-2016
// Bruno Bachelet - bruno@nawouak.net - http://www.nawouak.net
//
// This file is part of the B++ Library. This library is free software; you can redistribute it
// and/or modify it under the terms of the GNU Library General Public License as published by the
// Free Software Foundation; either version 2 of the License, or (at your option) any later
// version.
//
// This library 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 Library General Public License for more details (http://www.gnu.org).

/*DESCRIPTION*/
/* This module provides facilities to solve linear programs with the GLPK 4.54 package. */

// File Name //-------------------------------------------------------------------------------------
#line __LINE__ "linear_system/glpk.hpp"

// Guardian //--------------------------------------------------------------------------------------
#ifndef guLinearSystemGlpk
#define guLinearSystemGplk

// Headers //---------------------------------------------------------------------------------------
#include <cstdio> /*INCLUDE*/
#include <bpp/linear_system/solver.hpp> /*INCLUDE*/

#ifdef GLPK_YES
 extern "C" {
  #include <glpk.h> /*INCLUDE*/

  #undef str2dbl // May conflict with JNI.
 }
#endif

namespace bpp {

// Importation/Exportation //-----------------------------------------------------------------------
#ifdef LINEAR_SYSTEM_DLL
 #define dll_export DLL_EXPORT
#else
 #define dll_export DLL_IMPORT
#endif

// Namespaces //------------------------------------------------------------------------------------
#define public_area  linearSystemGlpk
#define private_area linearSystemGlpk_private

namespace public_area  { /*NAMESPACE*/ using namespace linearSystemSolver; }
namespace private_area { using namespace public_area; }

extern_module_name;

// Initialization //--------------------------------------------------------------------------------
#define iniLinearSystemGlpk
has_initializer;

// Macrocommands //---------------------------------------------------------------------------------
#ifdef GLPK_YES
 /*MACROCOMMAND*/
 /* The expression <CODE>prLine</CODE> is integrated into the source code if GLPK is used. */
 #define glpk_yes(prLine) prLine

 /*MACROCOMMAND*/
 /* The expression <CODE>prLine</CODE> is integrated into the source code if GLPK is not used. */
 #define glpk_no(prLine)
#else
 #define glpk_yes(prLine)
 #define glpk_no(prLine) prLine
#endif

// Types & Classes //-------------------------------------------------------------------------------
namespace public_area {
 //------------------------------------------------------------------------------------------Classes
 template <class prContent> class clGlpkSolver;
}

namespace private_area {}

// Functions Interface //---------------------------------------------------------------------------
namespace public_area  {}
namespace private_area { function int glpkPrint(tyPointer,tcString); }

// Errors //----------------------------------------------------------------------------------------
namespace public_area {
 /*ERROR*/ extern_error erGlpkImproperUse; /* Improper use of the GLPK solver. */

 /*ERROR*/ extern_error erGlpkIterationLimitExceeded;
 /* The maximum number of iterations is reached. */

 /*ERROR*/ extern_error erGlpkNumericalProblem;
 /* Numerical problem to solve the linear program. */

 /*ERROR*/ extern_error erGlpkTimeLimitExceeded;
 /* The maximum time of execution is reached. */

 /*ERROR*/ extern_error erGlpkUnknownError;
 /* An unknown error has occurred in the GLPK package. */

 glpk_no (
  /*ERROR*/ extern_error erGlpkNotInstalled;
  /* The library is not built for the use of the GLPK package. */
 )
}

// Constants & Variables //-------------------------------------------------------------------------
extern_dynamic_constant(private,clString,goTempoResultFileName,?);

// G l p k S o l v e r  Interface //----------------------------------------------------------------
namespace public_area {
 /*CLASS clGlpkSolver */ /* Represents the GLPK solver. */
 template <class prContent> class clGlpkSolver : public linearSystemSolver::clSolver<prContent> {
  //-----------------------------------------------------------------------------------------Private
  private_property clGlpkSolver & operator = (const clGlpkSolver &);
  //------------------------------------------------------------------------------------------Public
  public_property constructor clGlpkSolver(void);
  public_property constructor clGlpkSolver(const clGlpkSolver &);
  public_property destructor  clGlpkSolver(void);

  public_property tyBoolean available(void) const;
  public_property tyInteger analyzeResult(clInStream &,clLinearSystem<prContent> &) const;

  public_property void generateCommand(tcString,tcString,const clLinearSystem<prContent> &) const;

  public_property void      generateModel(clOutStream &,const clLinearSystem<prContent> &) const;
  public_property tcString  name(void) const;
  public_property void      outTemporary(clOutStream &) const;
  public_property void      removeTemporary(void) const;
  public_property tyInteger run(clLinearSystem<prContent> &) const;
 };
}

// Functions Inline //------------------------------------------------------------------------------
namespace public_area  {}
namespace private_area {}

// G l p k S o l v e r  Inline //-------------------------------------------------------------------
namespace public_area {
 //--------------------------------------------------------------------------------------Constructor
 /*METHOD clGlpkSolver */ /* Builds a GLPK solver. */
 template <class prContent>
 inline clGlpkSolver<prContent>::clGlpkSolver(void) : linearSystemSolver::clSolver<prContent>() {}
 //--------------------------------------------------------------------------------------Constructor
 /*METHOD clGlpkSolver */ /* Builds a GLPK solver from another one. */
 template <class prContent>
 inline clGlpkSolver<prContent>::clGlpkSolver(const clGlpkSolver<prContent> &)
 : clSolver<prContent>() {}
 //---------------------------------------------------------------------------------------Destructor
 /*METHOD clGlpkSolver */ /* Destructs the solver. */
 template <class prContent> inline clGlpkSolver<prContent>::~clGlpkSolver(void) {}
 //----------------------------------------------------------------------------------------Available
 /*METHOD clGlpkSolver */ /* Indicates if the GLPK solver is available. */
 template <class prContent> inline tyBoolean clGlpkSolver<prContent>::available(void) const {
  glpk_yes ( return (true); )
  glpk_no ( return (false); )
 }
 //------------------------------------------------------------------------------------AnalyzeResult
 /*METHOD clGlpkSolver */ /* Not used, throws an exception if called. */
 template <class prContent> inline
 tyInteger clGlpkSolver<prContent>::analyzeResult(clInStream &,clLinearSystem<prContent> &) const {
  send_inline_error(erGlpkImproperUse,"glpkSolver::analyzeResult");
  return (0);
 }
 //----------------------------------------------------------------------------------GenerateCommand
 /*METHOD clGlpkSolver */ /* Not used, throws an exception if called. */
 template <class prContent> inline
 void clGlpkSolver<prContent>::generateCommand(tcString,tcString,
                                               const clLinearSystem<prContent> &) const
 { send_inline_error(erGlpkImproperUse,"glpkSolver::generateCommand"); }
 //------------------------------------------------------------------------------------GenerateModel
 /*METHOD clGlpkSolver */ /* Not used, throws an exception if called. */
 template <class prContent> inline
 void clGlpkSolver<prContent>::generateModel(clOutStream &,const clLinearSystem<prContent> &) const
 { send_inline_error(erGlpkImproperUse,"glpkSolver::generateModel"); }
 //---------------------------------------------------------------------------------------------Name
 /*METHOD clGlpkSolver */ /* Returns the name of the solver. It is always "GLPK". */
 template <class prContent> inline tcString clGlpkSolver<prContent>::name(void) const
 { return ("GLPK"); }
}

// Function Implementation //-----------------------------------------------------------------------
namespace public_area  {}
namespace private_area {}

// G l p k S o l v e r  Implementation //-----------------------------------------------------------
namespace public_area {
 //-------------------------------------------------------------------------------------OutTemporary
 /*METHOD clGlpkSolver */
 /* Writes into a stream the last intermediate files used and generated by the GLPK software to
    solve a linear program. */
 template <class prContent>
 void clGlpkSolver<prContent>::outTemporary(clOutStream & agStream) const {
  method_name("glpkSolver::outTemporary");

  clInFile lcFile;

  agStream << "[>] Temporary Result File:" << end_line;
  open(lcFile,private_area::goTempoResultFileName->data(),ios::in|ios::binary);
  copy(agStream,lcFile,false);
  close(lcFile);
  if (lcFile.fail()) send_error(erFileReading);
 }
 //----------------------------------------------------------------------------------RemoveTemporary
 /*METHOD clGlpkSolver */
 /* Removes the last intermediate files used and generated by the GLPK package to solve a linear
    program. */
 template <class prContent> void clGlpkSolver<prContent>::removeTemporary(void) const {
  removeFile(private_area::goTempoResultFileName->data());
 }
 //----------------------------------------------------------------------------------------------Run
 /*METHOD clGlpkSolver */ /* Solves a linear program with the GLPK software. */
 template <class prContent>
 tyInteger clGlpkSolver<prContent>::run(clLinearSystem<prContent> & glpk_yes(agSystem)) const {
  method_name("glpkSolver::run");

  glpk_yes (
   typedef glp_prob *                                                      tyLinearSystem;
   typedef typename clLinearSystem<prContent>::cpVariableX::const_iterator cpIterator;
   typedef clCoefficientX::const_iterator                                  clIterator;
   typedef std_map(tyVariableKey,tyCardinal)                               clVariableX;

   tyCardinal  lcConstraintNo    = 0;
   cpIterator  lcCurrentVariable = agSystem.variables().begin();
   std::FILE * lcFile            = nil;
   cpIterator  lcLastVariable    = agSystem.variables().end();
   tyBoolean   lcMixedProblem    = false;
   tyCardinal  lcNbConstraint    = agSystem.constraints().size();
   tyCardinal  lcVariableNo      = 1;

   clConstraint<prContent> * lcConstraint;
   clIterator                lcCurrentCoefficient;
   tyInteger                 lcIteration;
   tyVariableKey             lcKey;
   clIterator                lcLastCoefficient;
   tyReturn                  lcResult;
   clString                  lcString;
   tyLinearSystem            lcSystem;
   tyReal                    lcValue;
   clVariableX               lcVariableX;

   std_vector(double) lcCoefficientS(1);
   std_vector(int)    lcColumnS(1);
   std_vector(int)    lcRowS(1);

   if (linearSystemSolver_private::goTemporaryKept) {
    lcFile=fopen(private_area::goTempoResultFileName->data(),"w");
    glp_term_hook(private_area::glpkPrint,lcFile);
   }
   else glp_term_hook(private_area::glpkPrint,nil);

   // System Creation //
   lcSystem=glp_create_prob();

   // MIP Program Checking //
   while (lcCurrentVariable!=lcLastVariable and not lcMixedProblem) {
    if ((*lcCurrentVariable).second->kind()==integralVariable) lcMixedProblem=true;
    ++lcCurrentVariable;
   }

   // Objective & Variables //
   if (agSystem.objective().kind()==maximum) glp_set_obj_dir(lcSystem,GLP_MAX);
   else glp_set_obj_dir(lcSystem,GLP_MIN);

   glp_add_cols(lcSystem,agSystem.variables().size());
   lcCurrentVariable=agSystem.variables().begin();

   while (lcCurrentVariable!=lcLastVariable) {
    lcKey=(*lcCurrentVariable).first;
    lcString=clString("x")+string(lcKey);
    glp_set_col_name(lcSystem,lcVariableNo,lcString.data());
    glp_set_obj_coef(lcSystem,lcVariableNo,agSystem.objective()[lcKey]);
    glp_set_col_bnds(lcSystem,lcVariableNo,GLP_LO,0.0,realMax());
    lcVariableX.insert(std_make_pair(lcKey,lcVariableNo));

    if (lcMixedProblem) {
     if ((*lcCurrentVariable).second->kind()==integralVariable)
      glp_set_col_kind(lcSystem,lcVariableNo,GLP_IV);
     else glp_set_col_kind(lcSystem,lcVariableNo,GLP_CV);
    }

    ++lcCurrentVariable;
    ++lcVariableNo;
   }

   // Constraints //
   glp_add_rows(lcSystem,lcNbConstraint);

   while (lcConstraintNo<lcNbConstraint) {
    lcString=clString("c"+string(lcConstraintNo));
    glp_set_row_name(lcSystem,lcConstraintNo+1,lcString.data());
    lcConstraint=&(agSystem[lcConstraintNo]);
    lcCurrentCoefficient=lcConstraint->coefficients().begin();
    lcLastCoefficient=lcConstraint->coefficients().end();

    while (lcCurrentCoefficient!=lcLastCoefficient) {
     lcValue=(*lcCurrentCoefficient).second;
     lcVariableNo=lcVariableX[(*lcCurrentCoefficient).first];

     if (not isEqual(lcValue,0.0)) {
      lcRowS.push_back(int(lcConstraintNo+1));
      lcColumnS.push_back(int(lcVariableNo));
      lcCoefficientS.push_back(double(lcValue));
     }

     ++lcCurrentCoefficient;
    }

    if (lcConstraint->kind()==inferiority)
     glp_set_row_bnds(lcSystem,lcConstraintNo+1,GLP_UP,realMin(),lcConstraint->boundary());
    else if (lcConstraint->kind()==superiority)
     glp_set_row_bnds(lcSystem,lcConstraintNo+1,GLP_LO,lcConstraint->boundary(),realMax());
    else
     glp_set_row_bnds(lcSystem,lcConstraintNo+1,GLP_FX,lcConstraint->boundary(),
                      lcConstraint->boundary());

    ++lcConstraintNo;
   }

   glp_load_matrix(lcSystem,lcRowS.size()-1,&(lcRowS[0]),&(lcColumnS[0]),&(lcCoefficientS[0]));

   // Linear Resolution //
   if (!lcMixedProblem) {
    glp_smcp lcParameters;

    glp_init_smcp(&lcParameters);
    lcParameters.presolve=GLP_ON;
    lcResult=glp_simplex(lcSystem,&lcParameters);

    if (lcResult==GLP_EITLIM) send_error(erGlpkIterationLimitExceeded);
    if (lcResult==GLP_ETMLIM) send_error(erGlpkTimeLimitExceeded);
    if (lcResult==GLP_ESING || lcResult==GLP_ECOND) send_error(erGlpkNumericalProblem);
    else if (lcResult!=GLP_ENOPFS && lcResult!=0) send_error(erGlpkUnknownError);

    lcResult=glp_get_status(lcSystem);
   }
   // MIP Resolution //
   else {
    glp_iocp lcParameters;

    glp_init_iocp(&lcParameters);
    lcParameters.presolve=GLP_ON;
    lcResult=glp_intopt(lcSystem,&lcParameters);

    if (lcResult==GLP_ETMLIM) send_error(erGlpkTimeLimitExceeded);
    if (lcResult==GLP_EMIPGAP) send_error(erGlpkNumericalProblem);
    else if (lcResult!=GLP_ENOPFS && lcResult!=0) send_error(erGlpkUnknownError);

    lcResult=glp_mip_status(lcSystem);
   }

   // Problem Not Feasible //
   if (lcResult!=GLP_OPT) {
    // System Destruction //
    glp_delete_prob(lcSystem);

    if (linearSystemSolver_private::goTemporaryKept and lcFile!=nil) {
     fclose(lcFile);
     glp_term_hook(nil,nil);
    }

    agSystem.solved()=false;
    return (-1);
   }

   // Result Analysis //
   lcCurrentVariable=agSystem.variables().begin();
   lcVariableNo=1;

   while (lcCurrentVariable!=lcLastVariable) {
    if (lcMixedProblem)
     (*lcCurrentVariable).second->value()=glp_mip_col_val(lcSystem,lcVariableNo);
    else glp_get_col_prim(lcSystem,lcVariableNo);

    ++lcCurrentVariable;
    ++lcVariableNo;
   }

   // System Destruction //
   lcIteration=glp_get_it_cnt(lcSystem);
   glp_delete_prob(lcSystem);

   if (linearSystemSolver_private::goTemporaryKept and lcFile!=nil) {
    fclose(lcFile);
    glp_term_hook(nil,nil);
   }

   agSystem.solved()=true;
   return (lcIteration);
  )

  glpk_no (
   send_error(erGlpkNotInstalled);
   return (0);
  )
 }
}

// End //-------------------------------------------------------------------------------------------
}
#undef dll_export
#undef public_area
#undef private_area
#endif
 
//==================================================================================================
// L i n e a r _ s y s t e m                                                         Implementation
// G l p k
//                                                                                By Bruno Bachelet
//==================================================================================================
// Copyright (c) 1999-2016
// Bruno Bachelet - bruno@nawouak.net - http://www.nawouak.net
//
// This file is part of the B++ Library. This library is free software; you can redistribute it
// and/or modify it under the terms of the GNU Library General Public License as published by the
// Free Software Foundation; either version 2 of the License, or (at your option) any later
// version.
//
// This library 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 Library General Public License for more details (http://www.gnu.org).

// File Name //-------------------------------------------------------------------------------------
#line __LINE__ "linear_system/glpk.cpp"

// DLL Belonging //---------------------------------------------------------------------------------
#define LINEAR_SYSTEM_DLL

// Headers //---------------------------------------------------------------------------------------
#include <cstdio> /*NEED*/
#include <bpp/file_name.hpp> /*NEED*/
#include <bpp/linear_system/glpk.hpp> /*INTERFACE*/

namespace bpp {

// Namespaces //------------------------------------------------------------------------------------
#define public_area  linearSystemGlpk
#define private_area linearSystemGlpk_private
#define dll_export   DLL_EXPORT

namespace public_area  {}
namespace private_area {}

static_module_name("Linear_system/Glpk");

// Initialization //--------------------------------------------------------------------------------
#undef iniLinearSystemGlpk
static_constant(private_area::clInitializer,goInitializer);

// Errors //----------------------------------------------------------------------------------------
namespace public_area {
 static_error erGlpkImproperUse;
 static_error erGlpkIterationLimitExceeded;
 static_error erGlpkNumericalProblem;
 static_error erGlpkTimeLimitExceeded;
 static_error erGlpkUnknownError;

 glpk_no ( static_error erGlpkNotInstalled; )
}

// Constants & Variables //-------------------------------------------------------------------------
dynamic_constant(clString,goTempoResultFileName);

// Static Members //--------------------------------------------------------------------------------
namespace public_area  {}
namespace private_area {}

// Functions Implementation //----------------------------------------------------------------------
namespace public_area {}

namespace private_area {
 //----------------------------------------------------------------------------------------GlpkPrint
 property int glpkPrint(tyPointer agStream,tcString agMessage) {
  if (agStream==nil) return (1);
  return (std::fprintf((std::FILE *)agStream,"%s",agMessage));
 }
}

// X X X  Implementation //-------------------------------------------------------------------------
namespace {}

// I n i t i a l i z e r  Implementation //---------------------------------------------------------
namespace private_area {
 //--------------------------------------------------------------------------------------------Start
 property void clInitializer::start(void) {
  if (atCounter++ == 0) {
   try {
    #include <bpp/modules.hpp> /*NEED*/
    registerStop(this);
    environment::informInitialization(goModuleName);

    erGlpkImproperUse.create("GLPK - Improper use.");
    erGlpkIterationLimitExceeded.create("GLPK - The maximum number of iterations is reached.");
    erGlpkNumericalProblem.create("GLPK - Numerical problem to solve the linear program.");
    erGlpkTimeLimitExceeded.create("GLPK - The maximum time of execution is reached.");
    erGlpkUnknownError.create("GLPK - An unknown error has occurred.");

    glpk_no ( erGlpkNotInstalled.create("GLPK isn't installed."); )

    goTempoResultFileName = new_object(clString(environment::temporaryLocation()+
                            fileNameSeparator()+LINEAR_SYSTEM_GLPK_TEMPORARY_FILE));
   }

   initializer_catch;
  }
 }
 //---------------------------------------------------------------------------------------------Stop
 property void clInitializer::stop(void) {
  try {
   environment::informTermination(goModuleName);

   delete_object(goTempoResultFileName);
  }

  initializer_catch;
 }
}

// End //-------------------------------------------------------------------------------------------
}