Program Listing for File PathSolver.cpp

Return to documentation for file (src/solvers/PathSolver.cpp)

extern "C" {
#include "License.h"
#include "MCP_Interface.h"
#include "Macros.h"
#include "Options.h"
#include "Output.h"
#include "Output_Interface.h"
#include "Path.h"
#include "PathOptions.h"
}
#include "solvers/PathSolver.h"

void Solvers::PATH::sort(int rows, int cols, int elements, int *row, int *col, double *data) {
  double *m_data;
  int    *m_start;
  int    *m_len;
  int    *m_row;

  int i = 0, cs = 0, ce = 0;

  m_start = new int[cols + 1];
  m_len   = new int[cols + 1];
  m_row   = new int[elements + 1];
  m_data  = new double[elements + 1];

  for (i = 0; i < cols; i++) {
     m_len[i] = 0;
  }

  for (i = 0; i < elements; i++) {
     if ((col[i] < 1) || (col[i] > cols)) {
        throw("column incorrect.\n");
     }

     if ((row[i] < 1) || (row[i] > rows)) {
        throw("column incorrect.\n");
     }

     m_len[col[i] - 1]++;
  }

  m_start[0] = 0;
  for (i = 1; i < cols; i++) {
     m_start[i]   = m_start[i - 1] + m_len[i - 1];
     m_len[i - 1] = 0;
  }
  m_len[i - 1] = 0;

  for (i = 0; i < elements; i++) {
     cs         = col[i] - 1;
     ce         = m_start[cs] + m_len[cs];
     m_row[ce]  = row[i];
     m_data[ce] = data[i];
     m_len[cs]++;
  }

  elements = 0;
  for (i = 0; i < cols; i++) {
     cs = m_start[i];
     ce = cs + m_len[i];

     while (cs < ce) {
        row[elements]  = m_row[cs];
        col[elements]  = i + 1;
        data[elements] = m_data[cs];
        elements++;
        cs++;
     }
  }
}

int Solvers::PATH::CreateLMCP(int    n,
                                        int    m_nnz,
                                        int    m_i[],
                                        int    m_j[],
                                        double m_ij[],
                                        double q[],
                                        double lb[],
                                        double ub[],
                                        double x[],
                                        double z[],
                                        int    verbose,
                                        double timeLimit) {
  MCP_Termination termination;
  Information     information;

  Output_SetLog(stdout);



  auto o = Options_Create();
  Path_AddOptions(o);
  Options_Default(o);



  double *xSol, *zSol;
  double  dnnz;
  int     i;


  if (verbose == 0) {
     Options_SetBoolean(o, "output", False);
     Options_SetBoolean(o, "output_errors", False);
     Options_SetBoolean(o, "output_crash_iterations", False);
     Options_SetBoolean(o, "output_major_iterations", False);
     Options_SetBoolean(o, "output_minor_iterations", False);
     Options_SetBoolean(o, "output_options", False);
     Options_SetBoolean(o, "output_warnings", False);
  }
  if (timeLimit > 0)
     Options_SetDouble(o, "time_limit", timeLimit);


  Options_SetInt(o, "cumulative_iteration_limit", 4e6);
  Options_SetInt(o, "major_iteration_limit", 1e6);
  Options_SetInt(o, "minor_iteration_limit", 1e5);
  Options_SetInt(o, "crash_iteration_limit", 5e2);
  Options_SetInt(o, "nms_memory_size", 1e2);
  Options_SetInt(o, "lemke_rank_deficiency_iterations", 1e2);
  // Very important!
  Options_Set(o, "lemke_start_type advanced");
  Options_SetBoolean(o, "crash_perturb", True);
  Options_SetDouble(o, "proximal_perturbation", 1);
  Options_SetInt(o, "crash_nbchange_limit", 50);
  Options_SetDouble(o, "convergence_tolerance", 1e-6);



  if (n == 0) {
     Options_Destroy(o);
     this->status = ZEROStatus::Numerical;
     return MCP_Solved;
  }

  dnnz = MIN(1.0 * m_nnz, 1.0 * n * n);

  if (verbose != 0) {
     fprintf(stdout,
                "%d row/cols, %f non-zeros, %3.2f%% dense.\n\n",
                n,
                dnnz,
                100.0 * dnnz / (1.0 * n * n));
  }

  this->Problem.n       = n;
  this->Problem.nnz     = m_nnz;
  this->Problem.x       = x;
  this->Problem.q       = q;
  this->Problem.lb      = lb;
  this->Problem.ub      = ub;
  this->Problem.m_start = new int[n];
  this->Problem.m_len   = new int[n];
  this->Problem.m_row   = new int[m_nnz + 1];
  this->Problem.m_data  = new double[m_nnz + 1];

  sort(n, n, m_nnz, m_i, m_j, m_ij);

  int m_index = 0, m_count = 0;
  for (i = 0; i < n; i++) {
     this->Problem.m_start[i] = m_count + 1;
     this->Problem.m_len[i]   = 0;

     while ((m_index < m_nnz) && (m_j[m_index] <= i + 1)) {
        if (m_ij[m_index] != 0) {
          this->Problem.m_len[i]++;
          this->Problem.m_row[m_count]  = m_i[m_index];
          this->Problem.m_data[m_count] = m_ij[m_index];
          m_count++;
        }
        m_index++;
     }
  }
  this->Problem.nnz = m_count;



  License_SetString(
        "2830898829&Courtesy&&&USR&45321&5_1_2021&1000&PATH&GEN&31_12_2025&0_0_0&6000&0_0");
  auto m = MCP_Create(this->Problem.n, this->Problem.nnz + 1);
  MCP_SetInterface(m, &this->PATH_Interface);
  MCP_SetPresolveInterface(m, &this->PATH_Presolve);
  if (verbose != 0)
     Output_SetInterface(&this->PATH_Output);
  MCP_Jacobian_Structure_Constant(m, static_cast<Boolean>(true));



  Information info;
  if (verbose != 0)
     info.generate_output = Output_Log | Output_Status | Output_Listing;
  info.use_start  = False;
  info.use_basics = False;

  try {
     termination = Path_Solve(m, &info);
  } catch (...) {
     throw ZEROException(ZEROErrorCode::SolverError, "PATHWrapper threw an exception");
  }


  switch (termination) {
  case MCP_Solved: {
     this->status = ZEROStatus::Solved;
     xSol         = MCP_GetX(m);
     zSol         = MCP_GetF(m);
     for (i = 0; i < n; i++) {
        x[i] = xSol[i];

        z[i] = zSol[i];
     }
  } break;
  case MCP_TimeLimit:
     this->status = ZEROStatus::TimeLimit;
     break;
  case MCP_Infeasible:
     this->status = ZEROStatus::NotSolved;
  default:
     this->status = ZEROStatus::Numerical;
  }



  MCP_Destroy(m);
  Options_Destroy(o);
  Path_Destroy();

  return termination;
}

Solvers::PATH::PATH(const arma::sp_mat   &M,
                          const arma::vec      &q,
                          const perps          &Compl,
                          const VariableBounds &Bounds,
                          arma::vec            &z,
                          arma::vec            &x,
                          double                timeLimit,
                          bool                  verbose) {

  // Note that for path z and x are inverted (z are variables, x equations definitions).
  int n   = 0;
  int nnz = 0;

  // M.save("LCP_M.dat", arma::csv_ascii);
  // q.save("LCP_q.dat",arma::csv_ascii);

  this->Filled = false;
  std::vector<double> _q, _Mij, _lb, _ub, _xsol, _zsol;
  std::vector<int>    _Mi, _Mj, _xmap, _zmap;
  unsigned int        row = 1; // Fortran style, we start from 1



  for (const auto p : Compl) {
     // For each complementarity
     // z[p.first] \perp x[p.second]


     {
        /*if (M.row(p.first).n_nonzero >0) {
          LOG_S(WARNING) << "Solvers::PathLCP: Empty row associated with z_"
                                              << std::to_string(p.first);
        }

        else {
            */
        int lb = Bounds.at(p.second).first;
        int ub = Bounds.at(p.second).second;

        // if (lb != ub)
        {
          ++n;
          // Avoid inserting fixed variables

          _lb.push_back(lb > 0 ? lb : 0);
          _ub.push_back(ub >= 0 ? ub : 1e20); // PATH will treat 1e20 as infinite. Note that negative UBs means infinity

          _q.push_back(q.at(p.first));
          _xmap.push_back(p.second);
          _zmap.push_back(p.first);

          _xsol.push_back(0);
          _zsol.push_back(0);

          ++row;
        } // end bounds
     }   // end empty row
  }     // end while


  // Sparse iterator for M
  for (arma::sp_mat::const_iterator it = M.begin(); it != M.end(); ++it) {
     _Mi.push_back(it.row() + 1);
     _Mj.push_back(it.col() + 1);
     _Mij.push_back(*it);
     ++nnz;
  }

  int stat = 0;
  /*
  _Mi.push_back(0);
  _Mj.push_back(0);
  _Mij.push_back(0);
  _q.push_back(0);
  _lb.push_back(0);
  _ub.push_back(0);
    */

  try {
     stat = this->CreateLMCP(n,
                                     nnz,
                                     &_Mi[0],
                                     &_Mj[0],
                                     &_Mij[0],
                                     &_q[0],
                                     &_lb[0],
                                     &_ub[0],
                                     &_xsol[0],
                                     &_zsol[0],
                                     verbose,
                                     timeLimit);
  } catch (...) {
     throw ZEROException(ZEROErrorCode::SolverError, "PATH threw an exception");
  }


  if (stat == 1) {
     LOG_S(1) << "Solvers::PathLCP: Found a solution";
     z.zeros(M.n_cols);
     x.zeros(M.n_rows);
     for (unsigned int i = 0; i < _xsol.size(); ++i) {
        z.at(_xmap.at(i)) = _xsol.at(i);
        x.at(_zmap.at(i)) = _zsol.at(i);
     }
     // z.print("z");
     // x.print("x");
     this->status = ZEROStatus::NashEqFound;
  } else {
     LOG_S(1) << "Solvers::PathLCP: No solution found (STATUS=" << std::to_string(status) << ")";
  }
}


void Solvers::PATH::C_bounds(int n, double *x, double *lb, double *ub) {
  int i;

  for (i = 0; i < n; i++) {
     x[i]  = this->Problem.x[i];
     lb[i] = this->Problem.lb[i];
     ub[i] = this->Problem.ub[i];
     // std::cout <<"\nx_"<<std::to_string(i)<<" in ["<<std::to_string(lb[i])<<",
     // "<<std::to_string(ub[i])<<"]";
  }
  // std::cout << "\n done\n";
}


int Solvers::PATH::C_function_evaluation(int n, double *x, double *f) {
  int    col, colStart, colEnd, row;
  double value;

  for (col = 0; col < n; col++) {
     f[col] = Problem.q[col];
  }

  for (col = 0; col < n; col++) {
     value = x[col];

     if (value != 0) {
        colStart = Problem.m_start[col] - 1;
        colEnd   = colStart + Problem.m_len[col];

        while (colStart < colEnd) {
          row = Problem.m_row[colStart] - 1;
          f[row] += Problem.m_data[colStart] * value;
          colStart++;
        }
     }
  }

  return 0;
}

int Solvers::PATH::C_jacobian_evaluation(int     n,
                                                      double *x,
                                                      int     wantf,
                                                      double *f,
                                                      int    *nnz,
                                                      int    *col_start,
                                                      int    *col_len,
                                                      int    *row,
                                                      double *data) {
  int element;

  if (wantf) {
     this->C_function_evaluation(n, x, f);
  }

  if (!Filled) {
     for (element = 0; element < Problem.n; element++) {
        col_start[element] = Problem.m_start[element];
        col_len[element]   = Problem.m_len[element];
     }

     for (element = 0; element < Problem.nnz; element++) {
        row[element]  = Problem.m_row[element];
        data[element] = Problem.m_data[element];
     }

     Filled = true;
  }

  *nnz = Problem.nnz;
  return 0;
}

void *Solvers::PATH::mcp_typ(void *dat, int nnz, int *typ) {
  int i;

  for (i = 0; i < nnz; i++) {
     typ[i] = PRESOLVE_LINEAR;
  }
  return dat;
}

void Solvers::PATH::C_problem_size(int *n, int *nnz) {
  *n   = this->Problem.n;
  *nnz = this->Problem.nnz + 1;
}
void Solvers::PATH::bounds(void *dat, int n, double *x, double *lb, double *ub) {
  auto *self = static_cast<Solvers::PATH *>(dat);
  self->C_bounds(n, x, lb, ub);
  return;
}
void Solvers::PATH::problem_size(void *dat, int *n, int *nnz) {
  auto *self = static_cast<Solvers::PATH *>(dat);
  self->C_problem_size(n, nnz);
}

void Solvers::PATH::start(void *dat) {
  auto *self   = static_cast<Solvers::PATH *>(dat);
  self->Filled = 0;
}

int Solvers::PATH::function_evaluation(void *dat, int n, double *x, double *f) {
  auto *self = static_cast<Solvers::PATH *>(dat);
  return self->C_function_evaluation(n, x, f);
}

int Solvers::PATH::jacobian_evaluation(void   *dat,
                                                    int     n,
                                                    double *x,
                                                    int     wantf,
                                                    double *f,
                                                    int    *nnz,
                                                    int    *col_start,
                                                    int    *col_len,
                                                    int    *row,
                                                    double *data) {
  auto *self = static_cast<Solvers::PATH *>(dat);
  return self->C_jacobian_evaluation(n, x, wantf, f, nnz, col_start, col_len, row, data);
}

void *Solvers::PATH::messageCB(void *dat, int mode, char *buf) {
  // std::cout << buf;
  return dat;
}