//==================================================================================================
// L i n e a r _ s y s t e m                                                              Interface
// C p l e x
//                                                                                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 CPLEX software. */

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

// Guardian //--------------------------------------------------------------------------------------
#ifndef guLinearSystemCplex
#define guLinearSystemCplex

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

namespace bpp {

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

// Namespaces //------------------------------------------------------------------------------------
#define public_area  linearSystemCplex
#define private_area linearSystemCplex_private

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

extern_module_name;

// Initialization //--------------------------------------------------------------------------------
#define iniLinearSystemCplex
has_initializer;

// Macrocommands //---------------------------------------------------------------------------------

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

namespace private_area {}

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

// Errors //----------------------------------------------------------------------------------------
namespace public_area {
 /*ERROR*/ extern_error erCplexUnknownError;
 /* An unknown error has occurred in the CPLEX software. */

 cplex_no (
  /*ERROR*/ extern_error erCplexNotInstalled;
  /* The library is not built for the use of the CPLEX software. */
 )
}

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

extern_dynamic_constant(private,clString,goDataLocation,?);

// C p l e x S o l v e r  Interface //--------------------------------------------------------------
namespace public_area {
 /*CLASS clCplexSolver */ /* Represents the CPLEX solver. */
 template <class prContent> class clCplexSolver : public linearSystemSolver::clSolver<prContent> {
  //-----------------------------------------------------------------------------------------Private
  private_property clCplexSolver & operator = (const clCplexSolver &);

  private_property tyInteger analyzeError(clInStream &) const;
  //------------------------------------------------------------------------------------------Public
  public_property constructor clCplexSolver(void);
  public_property constructor clCplexSolver(const clCplexSolver &);
  public_property destructor  clCplexSolver(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 {}

// C p l e x S o l v e r  Inline //-----------------------------------------------------------------
namespace public_area {
 //--------------------------------------------------------------------------------------Constructor
 /*METHOD clCplexSolver */ /* Builds a CPLEX solver. */
 template <class prContent> inline
 clCplexSolver<prContent>::clCplexSolver(void) : linearSystemSolver::clSolver<prContent>() {}
 //--------------------------------------------------------------------------------------Constructor
 /*METHOD clCplexSolver */ /* Builds a CPLEX solver from another one. */
 template <class prContent>
 inline clCplexSolver<prContent>::clCplexSolver(const clCplexSolver<prContent> &)
 : clSolver<prContent>() {}
 //---------------------------------------------------------------------------------------Destructor
 /*METHOD clCplexSolver */ /* Destructs the solver. */
 template <class prContent> inline clCplexSolver<prContent>::~clCplexSolver(void) {}
 //----------------------------------------------------------------------------------------Available
 /*METHOD clCplexSolver */ /* Indicates if the CPLEX solver is available. */
 template <class prContent> inline tyBoolean clCplexSolver<prContent>::available(void) const
 { return (exchangeCplex::valid()); }
 //---------------------------------------------------------------------------------------------Name
 /*METHOD clCplexSolver */ /* Returns the name of the solver. It is always "CPLEX". */
 template <class prContent> inline tcString clCplexSolver<prContent>::name(void) const
 { return ("CPLEX"); }
}

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

// C p l e x S o l v e r  Implementation //---------------------------------------------------------
namespace public_area {
 //-------------------------------------------------------------------------------------AnalyzeError
 template <class prContent>
 tyInteger clCplexSolver<prContent>::analyzeError(clInStream & agStream) const {
  method_name("cplexSolver::analyzeError");

  tyInteger lcErrorNo;

  agStream >> lcErrorNo;

  if (lcErrorNo==1101) return (-1);
  else {
   send_error(erCplexUnknownError);
   return (-1);
  }
 }
 //------------------------------------------------------------------------------------AnalyzeResult
 /*METHOD clCplexSolver */
 /* Analyzes from a stream the results given by the CPLEX software and stores them in a linear
    program structure. */
 template <class prContent>
 tyInteger clCplexSolver<prContent>::analyzeResult(clInStream & agStream,
                                                   clLinearSystem<prContent> & agSystem) const {
  method_name("cplexSolver::analyzeResult");

  typedef typename clLinearSystem<prContent>::cpVariableX::const_iterator cpIterator;

  tyCardinal lcCounter         = 0;
  cpIterator lcCurrentVariable = agSystem.variables().begin();
  cpIterator lcLastVariable    = agSystem.variables().end();

  tyCharacter   lcDummyString[256];
  tyVariableKey lcKey;
  tyInteger     lcNbIteration;
  tyReal        lcObjective;
  clString      lcString;

  agSystem.solved()=false;
  const_cast<clCplexSolver<prContent> *>(this)->atLastBranchingNodesNumber=0;

  // Number Iterations //
  while (lcCounter<2) {
   agStream >> lcString;
   if (lcString=="Error") return (analyzeError(agStream));
   if (lcString=="CPLEX>") lcCounter++;
  }

  agStream >> lcString;
  while (lcString!="Iterations" and lcString!="CPLEX>") {
   if (lcString=="Error") return (analyzeError(agStream));
   agStream >> lcString;
  }

  if (lcString=="Iterations") {
   agStream >> lcString >> lcNbIteration;
   agStream >> lcString;
   while (lcString!="CPLEX>") {
    if (lcString=="Error") return (analyzeError(agStream));

    if (lcString=="Nodes") {
     agStream >> lcString;
     agStream >> const_cast<clCplexSolver<prContent> *>(this)->atLastBranchingNodesNumber;
    }

    agStream >> lcString;
   }
  }

  // Objective Value //
  agStream >> lcString;

  while (lcString!="Objective" and lcString!="CPLEX>") {
   if (lcString=="Error") return (analyzeError(agStream));
   agStream >> lcString;
  }

  if (lcString=="CPLEX>") return (-1);
  agStream >> lcString >> lcObjective;

  // Variables Value //
  agStream >> lcString;

  while (lcString!="CPLEX>") {
   if (lcString=="Error") return (analyzeError(agStream));
   agStream >> lcString;
  }

  while (lcCurrentVariable!=lcLastVariable) {
   (*lcCurrentVariable).second->value()=0.0;
   lcCurrentVariable++;
  }

  agStream >> lcString;
  if (lcString=="Illegal") return (-1);
  if (lcString!="All") {
   agStream.getline(lcDummyString,256);
   agStream >> lcString;
  }

  while (lcString[0]=='x') {
   lcKey=cardinal(lcString.data()+1);
   agStream >> agSystem.variable(lcKey).value();
   agStream >> lcString;
  }

  if (lcString!="CPLEX>" and lcString!="All") send_error(erStreamReading);
  if (agStream.fail()) send_error(erStreamReading);
  agSystem.solved()=true;
  return (lcNbIteration);
 }
 //----------------------------------------------------------------------------------GenerateCommand
 /*METHOD clCplexSolver */
 /* Generates a file containing the commands sent to the CPLEX software to solve a given linear
    program. */
 template <class prContent>
 void clCplexSolver<prContent>::generateCommand(tcString agCommandFileName,
                                                tcString agModelFileName,
                                                const clLinearSystem<prContent> &) const {
  method_name("cplexSolver::generateCommand");

  clOutFile lcFile;

  open(lcFile,agCommandFileName,ios::out|ios::trunc);
  lcFile << "read " << agModelFileName << end_line;
  lcFile << "optimize" << end_line;
  lcFile << "display solution objective" << end_line;
  lcFile << "display solution variables -" << end_line;
  lcFile << "quit" << end_line;
  close(lcFile);

  if (lcFile.fail()) send_error(erFileWriting);
 }
 //------------------------------------------------------------------------------------GenerateModel
 /*METHOD clCplexSolver */
 /* Generates into a stream a model of a linear program that the CPLEX software can understand. */
 template <class prContent>
 void clCplexSolver<prContent>::generateModel(clOutStream & agStream,
                                              const clLinearSystem<prContent> & agSystem) const {
  method_name("cplexSolver::generateModel");

  typedef typename clLinearSystem<prContent>::cpVariableX::const_iterator cpIterator;
  typedef clCoefficientX::const_iterator                                  clIterator;

  tyCardinal  lcConstraintNo       = 0;
  tyCardinal  lcCounter            = 0;
  clIterator  lcCurrentCoefficient = agSystem.objective().coefficients().begin();
  cpIterator  lcCurrentVariable    = agSystem.variables().begin();
  tyBoolean   lcFirstCoefficient   = true;
  clIterator  lcLastCoefficient    = agSystem.objective().coefficients().end();
  cpIterator  lcLastVariable       = agSystem.variables().end();
  tyCardinal  lcNbConstraint       = agSystem.constraints().size();

  const clConstraint<prContent> * lcConstraint;
  tyReal                          lcValue;

  // Objective //
  if (agSystem.objective().kind()==maximum) agStream << "maximize" << end_line;
  else agStream << "minimize" << end_line;

  agStream << " obj: ";

  while (lcCurrentCoefficient!=lcLastCoefficient) {
   lcValue=(*lcCurrentCoefficient).second;

   if (lcFirstCoefficient) {
    agStream << lcValue << " x" << (*lcCurrentCoefficient).first;
    lcFirstCoefficient=false;
   }
   else if (lcValue<0) agStream << " - " << -lcValue << " x" << (*lcCurrentCoefficient).first;
   else if (lcValue>=0) agStream << " + " << lcValue << " x" << (*lcCurrentCoefficient).first;

   lcCurrentCoefficient++;
   lcCounter++;
   if (lcCurrentCoefficient!=lcLastCoefficient and lcCounter%10==0) agStream << end_line;
  }

  agStream << end_line << end_line;

  // Constraints //
  agStream << "subject to" << end_line;

  while (lcConstraintNo<lcNbConstraint) {
   agStream << " c" << lcConstraintNo << ": ";
   lcFirstCoefficient=true;
   lcCounter=0;
   lcConstraint=&(agSystem[lcConstraintNo]);
   lcCurrentCoefficient=lcConstraint->coefficients().begin();
   lcLastCoefficient=lcConstraint->coefficients().end();

   while (lcCurrentCoefficient!=lcLastCoefficient) {
    lcValue=(*lcCurrentCoefficient).second;

    if (lcFirstCoefficient) {
     agStream << lcValue << " x" << (*lcCurrentCoefficient).first;
     lcFirstCoefficient=false;
    }
    else if (lcValue<0) agStream << " - " << -lcValue << " x" << (*lcCurrentCoefficient).first;
    else if (lcValue>=0) agStream << " + " << lcValue << " x" << (*lcCurrentCoefficient).first;

    lcCurrentCoefficient++;
    lcCounter++;
    if (lcCurrentCoefficient!=lcLastCoefficient and lcCounter%10==0) agStream << end_line;
   }

   if (lcConstraint->kind()==inferiority) agStream << " <= ";
   else if (lcConstraint->kind()==superiority) agStream << " >= ";
   else agStream << " = ";

   agStream << lcConstraint->boundary() << end_line;
   lcConstraintNo++;
  }

  agStream << end_line;

  // Bounds //
  agStream << "bounds" << end_line;

  while (lcCurrentVariable!=lcLastVariable) {
   agStream << " 0 <= x" << (*lcCurrentVariable).first << " <= +infinity" << end_line;
   lcCurrentVariable++;
  }

  agStream << end_line;

  // Integrity //
  agStream << "integers" << end_line;
  lcCurrentVariable=agSystem.variables().begin();

  while (lcCurrentVariable!=lcLastVariable) {
   if ((*lcCurrentVariable).second->kind()==integralVariable)
    agStream << " x" << (*lcCurrentVariable).first << end_line;

   lcCurrentVariable++;
  }

  agStream << end_line;
  agStream << "end" << end_line;

  if (agStream.fail()) send_error(erStreamWriting);
 }
 //-------------------------------------------------------------------------------------OutTemporary
 /*METHOD clCplexSolver */
 /* Writes into a stream the last intermediate files used and generated by the CPLEX software to
    solve a linear program. */
 template <class prContent>
 void clCplexSolver<prContent>::outTemporary(clOutStream & agStream) const {
  method_name("cplexSolver::outTemporary");

  clInFile lcFile;

  agStream << "[>] Temporary Command File:" << end_line;
  open(lcFile,private_area::goTempoCommandFileName->data(),ios::in);
  copy(agStream,lcFile,false);
  close(lcFile);
  if (lcFile.fail()) send_error(erFileReading);

  agStream << end_line;
  agStream << "[>] Temporary Model File:" << end_line;
  open(lcFile,private_area::goTempoModelFileName->data(),ios::in);
  copy(agStream,lcFile,false);
  close(lcFile);
  if (lcFile.fail()) send_error(erFileReading);

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

  cplex_yes (
   using namespace private_area;

   clOutFile lcFile1;
   clInFile  lcFile2;
   tyInteger lcNbIteration;

   // Generation //
   open(lcFile1,goTempoModelFileName->data(),ios::out|ios::trunc);
   generateModel(lcFile1,agSystem);
   close(lcFile1);
   if (lcFile1.fail()) send_error(erFileWriting);

   // Execution //
   generateCommand(goTempoCommandFileName->data(),goTempoModelFileName->data(),agSystem);

   exchangeCplex::
   execute((clString("< ")+*goTempoCommandFileName+" > "+*goTempoResultFileName).data());

   // Analysis //
   open(lcFile2,goTempoResultFileName->data(),ios::in);
   lcNbIteration=analyzeResult(lcFile2,agSystem);
   close(lcFile2);
   if (lcFile2.fail()) send_error(erFileReading);

   if (not linearSystemSolver_private::goTemporaryKept) {
    removeFile(goTempoCommandFileName->data());
    removeFile(goTempoModelFileName->data());
    removeFile(goTempoResultFileName->data());
   }

   return (lcNbIteration);
  )

  cplex_no (
   send_error(erCplexNotInstalled);
   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
// C p l e x
//                                                                                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/cplex.cpp"

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

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

namespace bpp {

// Namespaces //------------------------------------------------------------------------------------
#define public_area  linearSystemCplex
#define private_area linearSystemCplex_private
#define dll_export   DLL_EXPORT

namespace public_area  {}
namespace private_area {}

static_module_name("Linear_system/Cplex");

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

// Errors //----------------------------------------------------------------------------------------
namespace public_area {
 static_error erCplexUnknownError;
 cplex_no ( static_error erCplexNotInstalled; )
}

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

dynamic_constant(clString,goDataLocation);

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

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

// 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);

    erCplexUnknownError.create("CPLEX - An unknown error has occurred.");
    cplex_no ( erCplexNotInstalled.create("CPLEX isn't installed."); )

    goTempoCommandFileName = new_object(clString(environment::temporaryLocation()+
                             fileNameSeparator()+LINEAR_SYSTEM_CPLEX_TEMPORARY_FILE_1));

    goTempoModelFileName = new_object(clString(environment::temporaryLocation()+
                           fileNameSeparator()+LINEAR_SYSTEM_CPLEX_TEMPORARY_FILE_2));

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

    goDataLocation = new_object(clString(environment::dataLocation()+fileNameSeparator()+
                     "linear_system"+fileNameSeparator()+"cplex"));
   }

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

   delete_object(goTempoCommandFileName);
   delete_object(goTempoModelFileName);
   delete_object(goTempoResultFileName);
   delete_object(goDataLocation);
  }

  initializer_catch;
 }
}

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