/********************************************************************************************
 Name:       DiscretizationSolver
 Discrete optimization for graph discretization
 Author:     J.Omer
 Sources:    C++
 License:    GNU General Public License v.2
 History:
 *********************************************************************************************/

#include "discretizationsolver.hpp"
#include <ctime>



//

ILOLAZYCONSTRAINTCALLBACK2(CyclesLazyConstraints, Instance &, inst, DiscretizationSolver&, solver) {

#ifdef VERBOSE
std::cout << "revorder: check lazy dicycle constraints" << std::endl;
#endif

// Search for cycles in the current solution digraph
//
// generate the adjacency lists of the vertices corresponding to the current
// cplex solution
float tolerance = 1e-06;
std::map<Vertex*, std::vector<Vertex*> > adjlist;
std::map<Vertex*, int > nbrefs;
for (Vertex* v : inst.vertices_) {
adjlist[v] = std::vector<Vertex*>();
nbrefs[v] = 0;
}
for (Vertex* u : inst.vertices_) {
if (solver.modeltype_ == WITNESS) {
if (getValue(solver.isvertexinclique(u) >= 1 - tolerance)) continue;
}
for (Vertex* v : u->neighbors_) {
if (getValue(solver.isbefore(u, v)) >= 1 - tolerance) {
adjlist[u].push_back(v);
nbrefs[v]++;
}
}
}
//
// search for the root of the digraph
Vertex* root = nullptr;
int minnbrefs = inst.nbvertices_;
if (solver.modeltype_ == WITNESS) {
for (Vertex* v : inst.vertices_) {
if (getValue(solver.isvertexinclique(v) >= 1 - tolerance)) {
root = v;
break;
}
}
} else {
for (Vertex* v : inst.vertices_) {
if (nbrefs[v] < minnbrefs) {
minnbrefs = nbrefs[v];
root = v;
if (minnbrefs < inst.L()) break;
}
}
}
//
// call the enumeration of cycles, returns false if there are none
std::vector<std::vector<Vertex*> > cycles;
bool iscyclic = solver.enumeratecycles(adjlist, root, cycles);

// Look for dicycle inequalities : we restrict the search to the dicycles
// that contain at least one edge in the distance graph
// besides that, the search is performed using brute force
int nbaddedcuts = 0;
IloEnv env = getEnv();
if (iscyclic) {
for (std::vector<Vertex*> path : cycles) {
IloExpr sumedges(env);
int cyclelength = path.size();
Vertex* u = path.back();
for (int i = 0; i < cyclelength; i++) {
Vertex* v = path[i];
sumedges += solver.isbefore(u, v);
u = v;
}
if ((solver.modeltype_ == WITNESS) && (cyclelength <= inst.L() + 1)) {
add(sumedges - solver.isvertexinclique(path.front()) <= cyclelength - 1).end();
nbaddedcuts++;
} else {
add(sumedges <= cyclelength - 1).end();
nbaddedcuts++;
}
}
#ifdef VERBOSE
std::cout << "revorder: number of violated dicycle constraints : " << nbaddedcuts << std::endl;
#endif
} else {
#ifdef VERBOSE
std::cout << "revorder: the graph is acyclic " << std::endl;
#endif
}
return;
}

// User branch callback that gives priority to branching on clique variables

ILOBRANCHCALLBACK2(BranchOnCliqueVariables, DiscretizationSolver&, solver, IloCplex::MIPCallbackI::NodeId&, nodeId) {
if (getBranchType() != BranchOnVariable)
return;

if (nodeId == getNodeId())
return;
nodeId = getNodeId();

// Branch on the fractionnary clique variable closest to 1
IntegerFeasibilityArray feas;
IloNumArray val;
try {
val = IloNumArray(getEnv());
feas = IntegerFeasibilityArray(getEnv());
getValues(val, solver.isclique_);
getFeasibilities(feas, solver.isclique_);
//
// get the clique variable with maximum integer infeasibility
IloInt bestvar = -1;
IloNum maxval = 0.3;
IloInt cols = solver.isclique_.getSize();
for (IloInt j = 0; j < cols; j++) {
if (feas[j] == Infeasible) {
if (val[j] >= 1 - 1.0e-6) {
bestvar = -1;
break;
}
if (std::min(val[j], 1 - val[j]) > maxval) {
bestvar = j;
maxval = std::min(val[j], 1 - val[j]);
}
} else if (val[j] >= 1 - 1.0e-6) {
bestvar = -1;
break;
}
}
//
// if there is at least one fractionnary clique variable create two branches
if (bestvar >= 0) {
//            HeuristicNode* data = new HeuristicNode();
//            data->fixcliqueindex(bestvar);
makeBranch(solver.isclique_[bestvar], val[bestvar], IloCplex::BranchUp, getObjValue());
makeBranch(solver.isclique_[bestvar], val[bestvar], IloCplex::BranchDown, getObjValue());
#ifdef VERBOSE
std::cout << "revorder: clique " << bestvar << " bounds " << getLB(solver.isclique_[bestvar]) << " " << getUB(solver.isclique_[bestvar]) << ", value " << val[bestvar] << ", feasibility " << feas[bestvar] << std::endl;
            std::cout << "revorder: branch callback: branch on the variable isclique_" << bestvar << std::endl;
#endif
}
//        else {
//            Vertex* bestu = NULL;
//            Vertex* bestv = NULL;
//            for (Vertex* u : solver.inst_->vertices_) {
//                std::map<Vertex*, IloNum> isbeforeval;
//                float sumrefs = 0;
//                for (Vertex* v : u->neighbors_) {
//                    isbeforeval[v] = getValue(solver.isbefore_[v][u]);
//                    sumrefs += isbeforeval[v];
//                }
//                if (sumrefs == solver.inst_->U()) {
//                    for (Vertex* v : u->neighbors_) {
//                        if (getFeasibility(solver.isbefore_[v][u]) == Infeasible) {
//                            if (std::min(isbeforeval[v], 1 - isbeforeval[v]) > maxval) {
//                                bestu = u;
//                                bestv = v;
//                                maxval = std::min(isbeforeval[v], 1 - isbeforeval[v]);
//                            }
//                        }
//                    }
//                }
//            }
//            if (bestu) {
//                // HeuristicNode* data = new HeuristicNode();
//                // data->fixcliqueindex(bestvar);
//                std::cout << "revorder: branch on isbefore_" << bestv->id_ << "_" << bestu->id_ << std::endl;
//                makeBranch(solver.isbefore_[bestv][bestu], getValue(solver.isbefore_[bestv][bestu]), IloCplex::BranchUp, getObjValue());
//                makeBranch(solver.isbefore_[bestv][bestu], getValue(solver.isbefore_[bestv][bestu]), IloCplex::BranchDown, getObjValue());
//            }
//        }
} catch (...) {

val.end();
feas.end();
throw;
}
val.end();
feas.end();
}


//
// constructors and destructor

Clique::Clique(std::vector<Vertex *> vert) : nbvertices_(vert.size()), vertices_(vert) {
}

Clique::~Clique() {
}

DiscretizationSolver::DiscretizationSolver(Instance* inst, Algorithm algo) : algo_(algo), inst_(inst) {
  this->bestnbfullref_ = -1;
  this->bestcliqueid_ = -1;
  for (Vertex* v : this->inst_->vertices_) {

    this->bestrank_[v] = -1;
    this->bestnbrefs_[v] = -1;
  }
  this->objvalue_ = inst->nbvertices_;
  this->cliquecutsmaxsize_ = inst->nbvertices_;
}
//

DiscretizationSolver::DiscretizationSolver(Instance* inst, Algorithm algo, Problem pb, Model mod, std::string optionfile, float timelimit) :
    problem_(pb), algo_(algo), modeltype_(mod), timelimit_(timelimit), inst_(inst) {
  this->bestnbfullref_ = -1;
  this->bestcliqueid_ = -1;
  for (Vertex* v : this->inst_->vertices_) {

    this->bestrank_[v] = -1;
    this->bestnbrefs_[v] = -1;
  }
  this->objvalue_ = inst->nbvertices_;
  if (!optionfile.empty())
  {
    std::ifstream infile(optionfile.c_str());
    if (!infile.is_open()) {
      throwError("revorder: error: the option file could not be opened");
    }
    // read the file line by line
    // one line contains the data relative to one option
    std::string line;
    while (std::getline(infile, line))
    {
      std::istringstream iss(line);
      char buf[100];
      std::string optionname;
      int optionvalue;

      //read the line and detect format errors
      if (!(iss >> buf)) {
        std::cerr << "revorder: error: there is a mistake with the  "
                     "format of the option file" <<std::endl;
        throw;
      } // error
      optionname = std::string(buf);
      //
      if (optionname.find("cliquecutsmaxsize") !=std::string::npos) {
        if (!(iss >> optionvalue)) {
          std::cerr << "revorder: error: there is a mistake with "
                       "the format of the option file" <<std::endl;
          throw;
        } // error
        this->cliquecutsmaxsize_ = std::min(optionvalue, inst->nbvertices_);
        continue;
      } else if (optionname.find("initialcyclesize") !=
          std::string::npos) {
        if (!(iss >> optionvalue)) {
          std::cerr << "revorder: error: there is a mistake with "
                       "the format of the option file" << std::endl;
          throw;
        } // error
        this->initialcyclesize_ =
            std::min(optionvalue, inst->nbvertices_);
        continue;
      } else if (optionname.find("decomposecomponents") !=
          std::string::npos) {
        if (!(iss >> optionvalue)) {
          std::cerr << "revorder: error: there is a mistake with "
                       "the format of the option file" <<std::endl;
          throw;
        } // error
        this->decomposecomponents_ = (bool) optionvalue;
        continue;
      }
    }
  } else {
    this->decomposecomponents_ = false;
    this->cliquecutsmaxsize_ = inst->nbvertices_;
    this->initialcyclesize_ = 3;
  }
}
//

DiscretizationSolver::~DiscretizationSolver() {

  for (Clique* clique : this->cliques_) delete clique;
  cliques_.clear();
}
//
// abstract solve method

int DiscretizationSolver::solve() {
  return 0;
}
//
// get the objective value of a given vertex order

int DiscretizationSolver::getobjvalue(std::map<Vertex*, int> rank, std::map<Vertex*, int> nbrefs) {

  int nbfullref = 0;
  for (Vertex* u : this->inst_->vertices_) {

    if (nbrefs[u] >= this->inst_->U()) nbfullref += 1;
  }
  return this->inst_->nbvertices_ - this->inst_->L() - nbfullref;

}
//
// get number of fully referenced vertices in an order

int DiscretizationSolver::getnbfullref(std::map<Vertex*, int> rank, std::map<Vertex*, int> nbrefs) {

  int nbfullref = 0;
  for (Vertex* u : this->inst_->vertices_) {

    if (nbrefs[u] >= this->inst_->U()) nbfullref += 1;
  }
  return nbfullref;

}
//
// return true if objval1 is better than objval2: this method is necessary, because the problem can be a minimization or a maximization

bool DiscretizationSolver::isabetterobjvalue(int objval1, int objval2) {

  return objval1 > objval2;
}
//
// compute the best greedy solution among those starting from the potential initial cliques and return true if the instance is feasible
// if allcliques is set to true, check every clique, otherwise, stop with the first feasible revorder

bool DiscretizationSolver::greedysolve(bool allcliques) {

  int U = this->inst_->U();
  int L = this->inst_->L();
  int nbvertices = this->inst_->nbvertices_;
  this->bestnbfullref_ = -1;
  this->bestcliqueid_ = -1;
  for (Vertex* u : this->inst_->vertices_) {
    this->bestisfullref_[u] = false;
    this->bestrank_[u] = -1;
    this->bestnbrefs_[u] = 0;
  }
  this->bestfullref_.clear();
  this->objvalue_ = this->inst_->nbvertices_;
  std::vector<Clique*> feasiblecliques;

  //
  // for each clique apply the constructive greedy that iteratively adds the
  // vertex with largest number of references
  std::map<Vertex*, int> rank;
  std::map<Vertex*, int> nbrefs;
  this->greedynbfullrefperclique_.clear();
  int feasiblecliqueid = -1;
  for (int initialcliqueindex = 0; initialcliqueindex < this->cliques_.size(); initialcliqueindex++) {
    //
    // data initialization
    Clique* initialclique = this->cliques_[initialcliqueindex];
    int currentrank = 1;
    std::vector<Vertex*> verticesleft = this->inst_->vertices_;
    int nbfullref = 0;
    for (Vertex * v : this->inst_->vertices_) {
      nbrefs[v] = 0;
      rank[v] = -1;
    }
    //
    // treat the initial clique first
    //
    // set the ranks of the vertices in the clique and set them as references of their neighbors
    for (Vertex* u : initialclique->vertices_) {
      rank[u] = currentrank++;
      for (Vertex* v : u->neighbors_) {
        if (rank[v] == -1) nbrefs[v]++;
      }
    }
    //
    // record the partially and fully referenced vertices
    std::vector<Vertex*> partialref;
    std::vector<Vertex*> fullref;
    for (Vertex* v : this->inst_->vertices_) {
      if (rank[v] == -1) {
        if (nbrefs[v] >= U) {
          fullref.push_back(v);
        } else if (nbrefs[v] >= L) {
          partialref.push_back(v);
        }
      }
    }
    //
    // include the vertex with largest number of references in the order until every vertex is treated or infeasibility of the initial clique is proved
    while (currentrank <= nbvertices) {
      if (!fullref.empty()) {
        Vertex* u = fullref.back();
        if (initialclique->initialpartialrefs_.empty()) {
          initialclique->initialfullrefs_.push_back(u);
        }
        rank[u] = currentrank++;
        nbfullref++;
        fullref.pop_back();
        for (Vertex* v : u->neighbors_) {
          if (rank[v] >= 1) continue;
          nbrefs[v]++;
          if (nbrefs[v] == U) {
            fullref.push_back(v);
          } else if (nbrefs[v] == L) {
            partialref.push_back(v);
          }
        }
      } else if (!partialref.empty()) {
        while (rank[partialref.back()] >= 1) {
          partialref.pop_back();
          if (partialref.empty()) {
            break;
          }
        }
        if (partialref.empty()) break;

        if (initialclique->initialpartialrefs_.empty()) {
          for (Vertex* v: partialref) {
            initialclique->initialpartialrefs_.push_back(v);
          }
        }
        Vertex * u = partialref.back();
        rank[u] = currentrank++;
        partialref.pop_back();
        for (Vertex* v : u->neighbors_) {
          if (rank[v] >= 0) continue;
          nbrefs[v]++;
          if (nbrefs[v] == U) {
            fullref.push_back(v);
          } else if (nbrefs[v] == L) {
            partialref.push_back(v);
          }
        }
      } else break;
    }

    if (currentrank - 1 == nbvertices) {
      feasiblecliqueid++;
      feasiblecliques.push_back(initialclique);
      this->greedynbfullrefperclique_.push_back(nbfullref);
      initialclique->greedyobjvalue_ = this->getobjvalue(rank, nbrefs);
      //
#ifdef VERBOSE
      std::cout << "revorder: greedy: clique computed solution with value " << initialclique->greedyobjvalue_ << std::endl;
#endif
      //
      // record the solution if a new incumbent is found
      if ((currentrank - 1 == nbvertices) && (initialclique->greedyobjvalue_ < this->objvalue_)) {
        //
#ifdef VERBOSE
        std::cout << "revorder: greedy: one new clique with cost " << initialclique->greedyobjvalue_ << '\n';
#endif
        //
        this->objvalue_ = initialclique->greedyobjvalue_;
        this->bestcliqueid_ = feasiblecliqueid;
        this->bestfullref_.clear();
        this->bestnbfullref_ = 0;
        for (Vertex* v : this->inst_->vertices_) {
          this->bestrank_[v] = rank[v];
          this->bestnbrefs_[v] = nbrefs[v];
          if (this->bestnbrefs_[v] >= U) {
            this->bestisfullref_[v] = true;
            this->bestfullref_.push_back(v);
            this->bestnbfullref_++;
          } else {
            this->bestisfullref_[v] = false;
          }
        }
        //
        // if we do not need to check every clique, stop with the first feasible revorder
        if (!allcliques) break;
      }
    } else {
#ifdef VERBOSE
      std::cout << "revorder: greedy: this clique is not a feasible start" << std::endl;
#endif
    }
  }
  //
  // build the list of cliques that will be used in the optimization
  this->cliques_.clear();
  for (Clique* clique : feasiblecliques) {
    this->cliques_.push_back(clique);
  }
  //
  // update the list of cliques ids each vertex is a member of
  for (Vertex* v : this->inst_->vertices_) {
    this->cliqueslist_[v].clear();
    this->cliqueidslist_[v].clear();
  }
  int id = 0;
  for (Clique* clique : this->cliques_) {
    for (Vertex* v : clique->vertices_) {
      this->cliqueslist_[v].push_back(clique);
      this->cliqueidslist_[v].push_back(id);
    }
    id++;
  }
  std::cout << "revorder: greedy: number of cliques after greedy solution = " << this->cliques_.size() << std::endl;
  //
  // Stop here and return false if the problem is infeasible
  if (this->bestnbfullref_ == -1) {
    std::cout << "revorder: greedy: no solution was found with the greedy, the problem is infeasible" << std::endl;
    return false;
  }
  //
  // Record the solution found
  std::cout << "revorder: greedy: number of part. ref. vertices in the best solution = " << this->objvalue_ << std::endl;
  this->greedynbfullref_ = this->bestnbfullref_;

  return true;
}
//
//
// run the search for a discretization order with the input options

bool DiscretizationSolver::discretizationorder(float timelimit) {

  bool feas_status = true;

  if (this->algo_ == GREEDY) {
    //
    // enumerate the potential initial cliques
    this->inst_->computeneighbors();
    feas_status = this->enumeratecliques(this->inst_->L() + 1);
    if (feas_status == false) {
      this->isfeasible_ = false;
      return false;
    }
    this->eliminateredundantcliques();
    //
    // run the greedy search
    feas_status = greedysolve();
    this->isfeasible_ = feas_status;
    return feas_status;
  }


  switch (this->algo_) {
    case IP:
      feas_status = minpartialip(timelimit);
      break;
    case CP:
      feas_status = cpvertex(timelimit);
      break;

    default:
      std::cout << "revorder: error with the problem and/or algorithm fed to the solver" << std::endl;
      throw;
  }


  return feas_status;
}
//
// greedy algorithm to get a reference primal bound
// try all the possible initial cliques as starting points of the greedy

bool DiscretizationSolver::greedymipstart(IloCplex & cplex) {
  //
  // set the best greedy solution as primal solution for the mip solution
  IloNumArray mipstartvalues(cplex.getEnv());
  IloNumVarArray mipstartvariables(cplex.getEnv());
  if (this->modeltype_ == VERTEXRANK) {
    for (Vertex* v : this->inst_->vertices_) {
      for (int k = 0; k < this->inst_->nbvertices_; k++) {
        mipstartvariables.add(this->hasrank_[v][k]);
        if (this->bestrank_[v] == k + 1) mipstartvalues.add(1);
        else mipstartvalues.add(0);
      }
    }
  } else {
    if (this->modeltype_ == WITNESS) {
      std::map<Vertex*, int> nbwitness;
      for (Vertex* u : this->inst_->vertices_) nbwitness[u] = 0;

      for (Vertex* u : this->inst_->vertices_) {
        if (this->bestrank_[u] <= this->inst_->L() + 1) {
          for (Vertex* v : u->neighbors_) {
            mipstartvariables.add(this->isbefore_[u][v]);
            mipstartvalues.add(1);
            nbwitness[v] += 1;
          }
        }
      }
      for (Vertex* u : this->inst_->vertices_) {
        if (this->bestrank_[u] >= this->inst_->L() + 2) {
          for (Vertex* v : u->neighbors_) {
            mipstartvariables.add(this->isbefore_[u][v]);
            if ((this->bestrank_[u] < this->bestrank_[v])) {
              if ((this->bestisfullref_[v]) && (nbwitness[v] < this->inst_->U())) {
                mipstartvalues.add(1);
                nbwitness[v] += 1;
              } else if ((!this->bestisfullref_[v]) && (nbwitness[v] < this->inst_->L())) {
                mipstartvalues.add(1);
                nbwitness[v] += 1;
              } else mipstartvalues.add(0);
            } else mipstartvalues.add(0);
          }
        }
      }
    } else {
      for (Vertex* u : this->inst_->vertices_) {
        for (Vertex* v : u->neighbors_) {
          if (u->id_ >= v->id_) continue;
          mipstartvariables.add(this->isbefore_[u][v]);
          mipstartvariables.add(this->isbefore_[v][u]);
          if (this->bestrank_[u] < this->bestrank_[v]) {
            mipstartvalues.add(1);
            mipstartvalues.add(0);
          } else {
            mipstartvalues.add(0);
            mipstartvalues.add(1);
          }
        }
      }
    }
    //
    // set the clique variables and corresponding clique variables
    if (this->modeltype_ == WITNESS) {
      for (Vertex* v : this->inst_->vertices_) {
        mipstartvariables.add(this->isvertexinclique_[v]);
        if (this->bestrank_[v] <= this->inst_->L() + 1) mipstartvalues.add(1);
        else mipstartvalues.add(0);
      }
    } else {
      for (int c = 0; c < this->cliques_.size(); c++) {
        mipstartvariables.add(this->isclique_[c]);
        if (c == this->bestcliqueid_) mipstartvalues.add(1);
        else mipstartvalues.add(0);
      }
    }
    //
    // full-ref variables
    for (Vertex* u : this->inst_->vertices_) {
      mipstartvariables.add(this->isfullref_[u]);
      if ((this->bestisfullref_[u]) || (this->bestrank_[u] <= this->inst_->L() + 1)) mipstartvalues.add(1);
      else mipstartvalues.add(0);
    }
    //
    // rank variables for RANKS model
    for (Vertex* v : this->inst_->vertices_) {
      if (this->modeltype_ == RANKS) {
        for (int k = 0; k < this->inst_->nbvertices_; k++) {
          mipstartvariables.add(this->hasrank_[v][k]);
          if (this->bestrank_[v] == k + 1) mipstartvalues.add(1);
          else mipstartvalues.add(0);
        }
        mipstartvariables.add(this->rank_[v]);
        mipstartvalues.add(this->bestrank_[v] - 1);
      }
    }
  }

  cplex.addMIPStart(mipstartvariables, mipstartvalues);
  mipstartvariables.end();
  mipstartvalues.end();

  return true;
}


//
// contraint programming CP^VERTEX from Bodur and MacNeil, 2019

bool DiscretizationSolver::cpvertex(float timelimit) {

  char name[50];
  int nbvertices = this->inst_->nbvertices_;
  int L = this->inst_->L();
  int U = this->inst_->U();
  IloEnv env;
  IloTimer cpuClockTotal(env);
  IloTimer cpuClockInit(env);
  cpuClockTotal.start();
  cpuClockInit.start();


  std::cout << std::endl;
  std::cout << "revorder: ----------------------------------------------------------" << std::endl;
  std::cout << "\nrevorder: cp: Run preprocessing procedures " << std::endl << std::endl;

  //
  // compute adjacency lists
  this->inst_->computeneighbors();
  //
  // identify the set of feasible initial cliques
  if (!this->enumeratecliques(this->inst_->L() + 1)) {
    this->isfeasible_ = false;
    return false;
  }
  this->eliminateredundantcliques();
  //
  // run the greedy algorithm for initial solution and reduction of initial cliques
  if (!this->greedysolve()) {
    std::cout << "Greedy solve found the problem to be infeasible at the time of mip warm start." << std::endl;
    return false;
  }

  //
  // initialize the model
  IloModel model(env);
  //
  // assign one unique index in {0,...,n-1} to each vertex
  std::map<Vertex*, int> indv;
  int ind = 0;
  for (Vertex* u : this->inst_->vertices_) {
    indv[u] = ind++;
  }

  //
  // initialize adjacency matrix
  IloIntArray adjmatrix(env, nbvertices * nbvertices);
  for (Vertex* u : this->inst_->vertices_) {
    for (Vertex* v : this->inst_->vertices_) {
      if (indv[v] < indv[u]) continue;
      if (u->isneighbor_[v]) {
        adjmatrix[nbvertices * indv[u] + indv[v]] = 1;
        adjmatrix[nbvertices * indv[v] + indv[u]] = 1;
      } else {
        adjmatrix[nbvertices * indv[u] + indv[v]] = 0;
        adjmatrix[nbvertices * indv[v] + indv[u]] = 0;
      }
    }
  }
  //
  // variables that set the rank of each vertex in the order
  //
  // isrankfullref[k] =1 if vertex v is fully-referenced and 0 otherwise
  IloBoolVarArray isrankfullref(env, nbvertices);
  for (int k = 0; k < nbvertices; k++) {
    isrankfullref[k] = IloBoolVar(env);
    sprintf(name, "isrankfullref_%i", k);
    isrankfullref[k].setName(name);
  }
  //
  // indatrank[r] = index of the vertex at rank r
  IloIntVarArray indatrank(env, nbvertices);
  for (int k = 0; k < nbvertices; k++) {
    indatrank[k] = IloIntVar(env, 0, nbvertices - 1);
    sprintf(name, "indatrank_%i", k);
    indatrank[k].setName(name);
  }
  //
  // objective function
  IloExpr obj(env);
  obj += 1;
  for (int k = L + 1; k < nbvertices; k++) {
    obj += (1 - isrankfullref[k]);
  }
  model.add(IloMinimize(env, obj));

  for (int i = 0; i <= L - 1; i++) {
    for (int j = i + 1; j <= L; j++) {
      model.add(IloElement(adjmatrix, nbvertices * indatrank[i] + indatrank[j]) == 1);
    }
  }

  for (int r = L + 1; r < nbvertices; r++) {
    IloExpr sumrefs(env);
    for (int i = 0; i <= r - 1; i++) {
      sumrefs += IloElement(adjmatrix, nbvertices * indatrank[i] + indatrank[r]);
    }
    model.add(sumrefs - (U - L) * isrankfullref[r] >= L);
    sumrefs.end();
  }

  model.add(IloAllDiff(env, indatrank));
  //
  // set as not in clique, all the vertices that do not belong to one of the possible clique
  std::map < Vertex*, bool> okforclique;
  for (Vertex* v : this->inst_->vertices_) okforclique[v] = false;
  for (Clique* c : this->cliques_) {
    for (Vertex* v : c->vertices_) {
      okforclique[v] = true;
    }
  }
  for (Vertex* v : this->inst_->vertices_) {
    if (!okforclique[v]) {
      for (int r = 0; r <= L; r++) {
        model.add(indatrank[r] != indv[v]);
      }
    }
  }
  //
  // load model in the CP solver
  IloCP cp(model);
  //
  // provide starting point with greedy solution
  //
  IloSolution sol(env);
  for (Vertex* v : this->inst_->vertices_) {
    if (this->bestrank_[v] - 1 >= L + 1) {
      sol.setValue(isrankfullref[this->bestrank_[v] - 1], bestisfullref_[v]);
    }
    sol.setValue(indatrank[this->bestrank_[v] - 1], indv[v]);
  }
  cp.setStartingPoint(sol);

  cpuClockInit.stop();
  //
  // set CP parameter
  cp.setParameter(IloCP::LogVerbosity, IloCP::Normal);
  cp.setParameter(IloCP::Workers, 1);
  cp.setParameter(IloCP::TimeLimit, timelimit - cpuClockInit.getTime());
#ifdef VERBOSE
  cp.setParameter(IloCP::LogVerbosity, IloCP::Normal);
#endif
  IloTimer cpuClock(env);
  cpuClock.start();
  try {
    cp.solve();
  } catch (IloException& e) {
    std::cerr << e << std::endl;
  }
  cpuClockTotal.stop();

  IloAlgorithm::Status status = cp.getStatus();
  std::cout << "revorder: ----------------------------------------------------------\n" << std::endl;
  std::cout << "revorder: CP optimizer solution status = " << status << std::endl;
  isfeasible_ = (status == IloAlgorithm::Feasible) || (status == IloAlgorithm::Optimal);
  isoptimal_ = (status == IloAlgorithm::Optimal);
  if (isfeasible_) {
    //
    // retrieve the information on the solution process
    this->objvalue_ = cp.getObjValue();
    totaltime_ = cpuClock.getTime();
    treatednodes_ = cp.getInfo(IloCP::NumberOfBranches);

    //
    // get the optimal order
    for (int r = 0; r < this->inst_->nbvertices_; r++) {
      this->bestrank_[this->inst_->vertices_[cp.getValue(indatrank[r])]] = r;
    }
    //
    // get the number of references of each vertex
    for (Vertex* u : this->inst_->vertices_) {
      this->bestnbrefs_[u] = 0;
      for (Vertex* v : u->neighbors_) {
        if (this->bestrank_[v] < this->bestrank_[u]) this->bestnbrefs_[u]++;
      }
    }
    //
    // get fully referenced vertices
    this->bestfullref_.clear();
    this->bestnbfullref_ = 0;
    this->bestisfullref_.clear();
    for (Vertex* u : this->inst_->vertices_) {
      if (this->bestnbrefs_[u] >= this->inst_->U()) {
        this->bestisfullref_[u] = true;
        this->bestfullref_.push_back(u);
        this->bestnbfullref_++;
      } else this->bestisfullref_[u] = false;
    }
    //
    // summary of cp execution
    std::cout << "revorder: total cpu time                    = " << cpuClockTotal.getTime() << " s" << std::endl;
    std::cout << "revorder: initialization cpu time           = " << cpuClockInit.getTime() << " s" << std::endl;
    std::cout << "revorder: number of branches explored       = " << treatednodes_ << std::endl;
    std::cout << "revorder: value of the objective function   = " << objvalue_ << std::endl;
    std::cout << "revorder: verification: number of part. ref. vertices = " << this->inst_->nbvertices_ - this->inst_->L() - this->bestnbfullref_ << std::endl;
  } else {
    std::string fileInfeasible("InfeasibleModel.lp");
    cp.exportModel(fileInfeasible.c_str());
    std::cout << "revorder: no feasible solution was found during the optimization!" << std::endl;
    std::cout << "revorder: check the file " << fileInfeasible << " to see the corresponding model" << std::endl;
  }
  //
  // delete CP objects
  cp.end();
  model.end();
  env.end();

  return isfeasible_;
}
//
// create the cplex model for the IP formulation described in Bodur an MacNeil, 2019

void DiscretizationSolver::defineminpartial_vertexrank(IloModel & model) {
  IloEnv env = model.getEnv();
  char name[50];
  int nbvertices = this->inst_->nbvertices_;
  int L = this->inst_->L();
  int U = this->inst_->U();

  //////////////////////////////////////////////////////////////////////////////
  // Declare the variables
  //////////////////////////////////////////////////////////////////////////////
  //
  // variables that set the rank of each vertex in the order
  // - hasrank_[v,k] = 1 if vertex i has rank k in the discretization order
  //  in this model, we use these variables only for the first L vertices
  //  of the cliques (if modeltype <= 1)
  for (Vertex* v : this->inst_->vertices_) {
    hasrank_[v] = IloBoolVarArray(env, nbvertices);
    for (int k = 0; k < nbvertices; k++) {
      sprintf(name, "hasrank_%i_%i", v->id_, k);
      hasrank_[v][k].setName(name);
    }
  }
  //
  // isrankfullref[k] =1 if vertex v is fully-referenced and 0 otherwise
  IloBoolVarArray isrankfullref(env, nbvertices);
  for (int k = 0; k < nbvertices; k++) {
    isrankfullref[k] = IloBoolVar(env);
    sprintf(name, "isrankfullref_%i", k);
    isrankfullref[k].setName(name);
  }
  //
  // isfullrefatrank[u,k] = 1 if vertex u is fully-referenced and is at rank k
  BoolVarMapArray isfullrefatrank;
  for (Vertex* v : this->inst_->vertices_) {
    isfullrefatrank[v] = IloBoolVarArray(env, nbvertices);
    for (int k = 0; k < this->inst_->nbvertices_; k++) {
      sprintf(name, "isfullrefatrank%i_%i", v->id_, k);
      isfullrefatrank[v][k].setName(name);
    }
  }

  //////////////////////////////////////////////////////////////////////////////
  // Set the cplex model that will be solved
  //////////////////////////////////////////////////////////////////////////////
  //
  // objective function
  IloExpr obj(env);
  obj += 1;
  for (int k = 0; k < nbvertices; k++) {
    obj += (1 - isrankfullref[k]);
  }
  model.add(IloMinimize(env, obj));

  //
  // each rank of the order is taken by exactly one vertex
  IloRangeArray ctaryOneVertexPerRank(env);
  for (int k = 0; k < nbvertices; k++) {
    IloExpr sumvertices(env);
    for (Vertex* v : this->inst_->vertices_) sumvertices += hasrank_[v][k];
    ctaryOneVertexPerRank.add(sumvertices == 1);
    sprintf(name, "ctaryOneVertexPerRank_%i", k);
    ctaryOneVertexPerRank[k].setName(name);
  }
  model.add(ctaryOneVertexPerRank);
  //
  // each vertex has exactly one rank and his rank is computed
  IloRangeArray ctaryOneRankPerVertex(env);
  IloRangeArray ctaryComputeRank(env);
  int nbcons = 0;
  for (Vertex* u : this->inst_->vertices_) {
    IloExpr sumhasrank(env);
    for (int k = 0; k < nbvertices; k++) sumhasrank += hasrank_[u][k];
    ctaryOneRankPerVertex.add(sumhasrank == 1);
    sprintf(name, "ctaryOneRankPerVertex_%i", u->id_);
    ctaryOneRankPerVertex[nbcons].setName(name);
  }
  model.add(ctaryOneRankPerVertex);
  //
  // Guarantee that isfullrefatrank[v][k] =1 only if v has rank k and has at least U references
  IloRangeArray ctaryIsFullRefAtLevel(env);
  for (int k = 0; k <= L; k++) {
    ctaryIsFullRefAtLevel.add(isrankfullref[k] == 1);
  }
  for (int k = 0; k < this->inst_->nbvertices_; k++) {
    for (Vertex* v : this->inst_->vertices_) {
      ctaryIsFullRefAtLevel.add(hasrank_[v][k] - (1 - isrankfullref[k]) - isfullrefatrank[v][k] <= 0);
    }
  }
  model.add(ctaryIsFullRefAtLevel);
  //
  // Discretization constraints: every vertex must be partially-referenced and those with more than U references are fully-referenced
  IloRangeArray ctaryLrefs(env);
  IloRangeArray ctaryClique(env);
  IloRangeArray ctaryFullRef(env);
  nbcons = 0;
  for (Vertex* u : this->inst_->vertices_) {
    //
    // first deal with the vertices of the initial clique
    for (int k = 0; k <= L; k++) {
      IloExpr sumrefs(env);
      for (Vertex* v : u->neighbors_) {
        for (int l = 0; l <= k - 1; l++) sumrefs += hasrank_[v][l];
      }
      ctaryClique.add(sumrefs - k * hasrank_[u][k] >= 0);
    }
    //
    // then deal with the rest of the order
    for (int k = L + 1; k < nbvertices; k++) {
      IloExpr sumrefs(env);
      for (Vertex* v : u->neighbors_) {
        for (int l = 0; l <= k - 1; l++) sumrefs += hasrank_[v][l];
      }
      ctaryLrefs.add(sumrefs - L * hasrank_[u][k] >= 0);
      ctaryFullRef.add(sumrefs - U * isfullrefatrank[u][k] >= 0);
    }
  }
  model.add(ctaryClique);
  model.add(ctaryLrefs);
  model.add(ctaryFullRef);
  //
  // set as not in clique, all the vertices that do not belong to one of the possible clique
  //        std::map < Vertex*, bool> okforclique;
  //        for (Vertex* v : this->inst_->vertices_) okforclique[v] = false;
  //        for (Clique* c : this->cliques_) {
  //            for (Vertex* v : c->vertices_) {
  //                okforclique[v] = true;
  //            }
  //        }
  //        for (Vertex* v : this->inst_->vertices_) {
  //            if (!okforclique[v]) {
  //                for (int k = 0; k <= L; k++) {
  //
  //                    model.add(hasrank_[v][k] == 0);
  //                }
  //            }
  //        }
}

//
// define the max fully-referenced cplex model

void DiscretizationSolver::defineminpartialip(IloModel & model) {

  IloEnv env = model.getEnv();
  char name[50];
  int nbvertices = this->inst_->nbvertices_;
  int L = this->inst_->L();
  int U = this->inst_->U();
  int nbcliques = this->cliques_.size();
  int nbcons = 0;

  //////////////////////////////////////////////////////////////////////////////
  // Declare the variables
  //////////////////////////////////////////////////////////////////////////////

  this->declareipvariables(model);


  //////////////////////////////////////////////////////////////////////////////
  // Set the cplex model that will be solved
  //////////////////////////////////////////////////////////////////////////////
  //
  // objective function
  IloExpr obj(env);
  obj += 1;
  for (Vertex* v : this->inst_->vertices_) {
    obj += 1 - isfullref_[v];
  }
  model.add(IloMinimize(env, obj));

  //////////////////////////////////////////////////////////////////////////////
  // Constraints that set the vertices belonging to the initial clique
  // Enumerate all the cliques and choose one
  //////////////////////////////////////////////////////////////////////////////
  //
  // choose one clique among those that have been enumerated
  if (this->modeltype_ != WITNESS) {
    IloRange ctOneClique(env, 1, 1, "ctOneClique");
    IloExpr sumcliques(env);
    for (int c = 0; c < nbcliques; c++) sumcliques += isclique_[c];
    ctOneClique = (sumcliques == 1);
    model.add(ctOneClique);

    if (this->modeltype_ == CCG) {
      for (int c = 0 ; c < nbcliques ; c++) {
        Clique* initialclique = this->cliques_[c];
        if (initialclique->greedyobjvalue_ >= 2) {
          IloExpr sumpartref(env);
          for (Vertex* v: initialclique->initialpartialrefs_) sumpartref += (1-isfullref_[v]);
          model.add(sumpartref >= isclique_[c]);
          for (Vertex* v: initialclique->initialfullrefs_) model.add(isfullref_[v] >= isclique_[c]);
        }
      }
    }
  }
  //
  // clique constraints in WITNESS: potential cliques are not enumerated
  if (this->modeltype_ == WITNESS) {
    //
    // vertices in the clique are not counted as part. ref
    IloRangeArray ctaryCliquesAreFull(env);
    nbcons = 0;
    for (Vertex* v : this->inst_->vertices_) {
      ctaryCliquesAreFull.add(isfullref_[v] - isvertexinclique_[v] >= 0);
      sprintf(name, "ctaryCliquesAreFull%i", v->id_);
      ctaryCliquesAreFull[nbcons++].setName(name);
    }
    model.add(ctaryCliquesAreFull);
    //
    // there must exactly L+1 vertices in the initial clique
    IloRange ctLPlusOneInClique(env, 1, 1, "ctLPlusOneInClique");
    IloExpr sumvertex(env);
    for (Vertex* v : this->inst_->vertices_) sumvertex += isvertexinclique_[v];
    ctLPlusOneInClique = (sumvertex == this->inst_->L() + 1);
    model.add(ctLPlusOneInClique);
    sumvertex.end();
    //
    // two non-adjacent vertices are not in the initial clique
    IloRangeArray ctaryCliquesAreAdjacent(env);
    nbcons = 0;
    for (Vertex* u : this->inst_->vertices_) {
      for (Vertex* v : this->inst_->vertices_) {
        if ((u == v) || (u->isneighbor(v))) continue;
        else {
          ctaryCliquesAreAdjacent.add(isvertexinclique_[u] + isvertexinclique_[v] <= 1);
          sprintf(name, "ctaryCliquesAreAdjacent%i_%i", u->id_, v->id_);
          ctaryCliquesAreAdjacent[nbcons++].setName(name);
        }
      }
    }
    model.add(ctaryCliquesAreAdjacent);
    //
    // vertices in the clique are witness to all their neighbors
    IloRangeArray ctaryCliqueIsWitness(env);
    nbcons = 0;
    for (Vertex* u : this->inst_->vertices_) {
      for (Vertex* v : u->neighbors_) {
        ctaryCliqueIsWitness.add(isbefore_[u][v] - isvertexinclique_[u] >= 0);
        sprintf(name, "ctaryCliqueIsWitness_%i_%i", u->id_, v->id_);
        ctaryCliqueIsWitness[nbcons++].setName(name);
      }
    }
    model.add(ctaryCliqueIsWitness);
    //
    // set as not in clique, all the vertices that do not belong to one of the possible clique
    //            std::map < Vertex*, bool> okforclique;
    //            for (Vertex* v : this->inst_->vertices_) okforclique[v] = false;
    //            for (Clique* c : this->cliques_) {
    //                for (Vertex* v : c->vertices_) {
    //                    okforclique[v] = true;
    //                }
    //            }
    //            for (Vertex* v : this->inst_->vertices_) {
    //                if (!okforclique[v]) {
    //                    model.add(isvertexinclique_[v] == 0);
    //                }
    //            }
  }

  //////////////////////////////////////////////////////////////////////////////
  // Constraints that guarantee that the solution digraph is acyclic
  // this is where the different models mostly differ:
  // - CYCLES forbid all the two-cycles and most three-cycles
  // - CCG, WITNESS: forbid only some cycles of the distance graph,  and
  // generate the other cycles through a callback
  // - RANKS: includes MTZ kinds of constraints instead of cycle
  // constraints, but add cycle cuts as valid inequalities anyways
  //////////////////////////////////////////////////////////////////////////////
  IloRangeArray ctaryNoTwoCycle(env);
  IloRangeArray ctaryNoThreeCycle(env);
  IloRangeArray ctaryNoFourCycle(env);
  //
  // in model CYCLES, we need to make sure that the solution is acyclic by
  // guaranteeing total order and transitivity
  if (this->modeltype_ == CYCLES) {
    nbcons = 0;
    for (Vertex* u : this->inst_->vertices_) {
      for (Vertex* v : this->inst_->vertices_) {
        if (v->id_ <= u->id_) continue;
        ctaryNoTwoCycle.add(isbefore_[u][v] + isbefore_[v][u] == 1);
        sprintf(name, "ctaryNoTwoCycle_%i_%i", u->id_, v->id_);
        ctaryNoTwoCycle[nbcons++].setName(name);
      }
    }
    model.add(ctaryNoTwoCycle);
    //
    // forbid every three-cycle but those that have no common edge with
    // the distance graph (we proved that this is sufficient)
    nbcons = 0;
    for (Vertex* u : this->inst_->vertices_) {
      for (Vertex* v : u->neighbors_) {
        if (v->id_ <= u->id_) continue;
        for (Vertex* w : this->inst_->vertices_) {
          if (w->id_ <= v->id_) continue;
          ctaryNoThreeCycle.add(
              isbefore_[u][v] + isbefore_[v][w] + isbefore_[w][u]
                  <= 2);
          sprintf(name, "ctaryNoThreeCycle_%i_%i_%i", u->id_, v->id_, w->id_);
          ctaryNoThreeCycle[nbcons++].setName(name);
          ctaryNoThreeCycle.add(
              isbefore_[w][v] + isbefore_[v][u] + isbefore_[u][w]
                  <= 2);
          sprintf(name, "ctaryNoThreeCycle_%i_%i_%i",
                  w->id_, v->id_, u->id_);
          ctaryNoThreeCycle[nbcons++].setName(name);
        }
      }
    }
    model.add(ctaryNoThreeCycle);
  }
  //
  // initially forbid cycles of the distance graphs with size <=
  // initialcyclesize_ in models CCG, WITNESS and RANKS;
  // in CCG and WITNESS; the remaining cycles cuts are added as lazy
  // constraints
  if ((this->modeltype_ == CCG || this->modeltype_ == WITNESS || this->modeltype_ == RANKS) && (this->initialcyclesize_ >= 2)) {
    // - forbid every two-cycle
    // - only the edges of the distance graph need to be considered in
    // the two models CCG and RANKS
    nbcons = 0;
    for (Vertex* u : this->inst_->vertices_) {
      for (Vertex* v : u->neighbors_) {
        if (v->id_ <= u->id_) continue;

        if (this->modeltype_ == WITNESS) {
          ctaryNoTwoCycle.add(
              isbefore_[u][v] + isbefore_[v][u] - isvertexinclique_[u]
                  <= 1);
        } else {
          ctaryNoTwoCycle.add(isbefore_[u][v] + isbefore_[v][u] == 1);
        }
        sprintf(name, "ctaryNoTwoCycle_%i_%i", u->id_, v->id_);
        ctaryNoTwoCycle[nbcons++].setName(name);
      }
    }
    model.add(ctaryNoTwoCycle);

    // forbid the three-cycles of the distance graph
    if (this->initialcyclesize_ >= 3) {
      nbcons = 0;
      for (Vertex* u : this->inst_->vertices_) {
        for (Vertex* v : u->neighbors_) {
          if (v->id_ <= u->id_) continue;
          for (Vertex* w : v->neighbors_) {
            if (!w->isneighbor(u)) continue;
            if (w->id_ <= v->id_) continue;
            if (this->modeltype_ == WITNESS) {
              ctaryNoThreeCycle.add(
                  isbefore_[u][v] + isbefore_[v][w]
                      + isbefore_[w][u] - isvertexinclique_[u] <= 2);
              ctaryNoThreeCycle.add(
                  isbefore_[w][v] + isbefore_[v][u]
                      + isbefore_[u][w] - isvertexinclique_[u] <= 2);
            } else {
              ctaryNoThreeCycle.add(
                  isbefore_[u][v] + isbefore_[v][w] + isbefore_[w][u] <= 2);
              ctaryNoThreeCycle.add(
                  isbefore_[w][v] + isbefore_[v][u] + isbefore_[u][w] <= 2);

            }
            sprintf(name, "ctaryNoThreeCycle_%i_%i_%i",
                    u->id_, v->id_, w->id_);
            ctaryNoThreeCycle[nbcons++].setName(name);
            sprintf(name, "ctaryNoThreeCycle_%i_%i_%i",
                    w->id_, v->id_, u->id_);
            ctaryNoThreeCycle[nbcons++].setName(name);
          }
        }
      }
      model.add(ctaryNoThreeCycle);
    }
    //
    // forbid every four-cycle: the complex verifications aim at adding only
    // one constraint per four-cycle that is not implied by two three-cycles
    if (this->initialcyclesize_ >= 4) {
      nbcons = 0;
      for (Vertex* u : this->inst_->vertices_) {
        for (Vertex* v : u->neighbors_) {
          if (v->id_ <= u->id_) continue;
          for (Vertex* w : v->neighbors_) {
            for (Vertex* x : w->neighbors_) {
              if (!x->isneighbor(u)) continue;
              if (w->id_ <= x->id_ && w->id_ <= v->id_) continue;
              if (x->isneighbor(v) && w->isneighbor(u)) continue;

              if (this->modeltype_ == WITNESS) {
                ctaryNoFourCycle.add(
                    isbefore_[u][v] + isbefore_[v][w] + isbefore_[w][x]
                        + isbefore_[x][u] - isvertexinclique_[u] <= 3);
                ctaryNoFourCycle.add(
                    isbefore_[x][w] + isbefore_[w][v] + isbefore_[v][u]
                        + isbefore_[u][x] - isvertexinclique_[u] <= 3);
              } else {
                ctaryNoFourCycle.add(
                    isbefore_[u][v] + isbefore_[v][w] + isbefore_[w][x]
                        + isbefore_[x][u] <= 3);
                ctaryNoFourCycle.add(
                    isbefore_[x][w] + isbefore_[w][v] + isbefore_[v][u]
                        + isbefore_[u][x] <= 3);

              }
              sprintf(name, "ctaryNoFourCycle_%i_%i_%i_%i",
                      u->id_, v->id_, w->id_, x->id_);
              ctaryNoFourCycle[nbcons++].setName(name);
              sprintf(name, "ctaryNoFourCycle_%i_%i_%i_%i",
                      x->id_, w->id_, v->id_, u->id_);
              ctaryNoFourCycle[nbcons++].setName(name);
            }
          }
        }
      }
      model.add(ctaryNoFourCycle);
    }
  }

  // - RANKS: first, make sure that each rank is taken by exaclty one vertex
  //	then, use MTZ constraints instead of cycle constraints
  if (this->modeltype_ == RANKS) {
    //
    // each rank of the order is taken by exactly one vertex
    IloRangeArray ctaryOneVertexPerRank(env);
    for (int k = 0; k < nbvertices; k++) {
      IloExpr sumvertices(env);
      for (Vertex* v : this->inst_->vertices_) sumvertices += hasrank_[v][k];
      ctaryOneVertexPerRank.add(sumvertices == 1);
      sprintf(name, "ctaryOneVertexPerRank_%i", k);
      ctaryOneVertexPerRank[k].setName(name);
    }
    model.add(ctaryOneVertexPerRank);
    //
    // each vertex has exactly one rank and his rank is computed
    IloRangeArray ctaryOneRankPerVertex(env);
    IloRangeArray ctaryComputeRank(env);
    nbcons = 0;
    for (Vertex* u : this->inst_->vertices_) {
      IloExpr sumhasrank(env);
      IloExpr sumranks(env);

      for (int k = 0; k < nbvertices; k++) sumhasrank += hasrank_[u][k];
      for (int k = 0; k < nbvertices; k++) sumranks += k * hasrank_[u][k];
      ctaryOneRankPerVertex.add(sumhasrank == 1);
      ctaryComputeRank.add(sumranks - rank_[u] == 0);
      sprintf(name, "ctaryOneRankPerVertex_%i", u->id_);
      ctaryOneRankPerVertex[nbcons].setName(name);
      sprintf(name, "ctaryComputeRank_%i", u->id_);
      ctaryComputeRank[nbcons++].setName(name);
    }
    model.add(ctaryOneRankPerVertex);
    model.add(ctaryComputeRank);
    //
    // Miller-Tucker-Zemlin constraints to ensure that the direction of the
    // distance graph edges respect the order of the ranks
    IloRangeArray ctaryRankTransitivity(env);
    nbcons = 0;
    for (Vertex* u : this->inst_->vertices_) {
      for (Vertex* v : u->neighbors_) {
        if (v->id_ <= u->id_) continue;
        ctaryRankTransitivity.add(
            1 <= nbvertices * isbefore_[u][v] - rank_[v] + rank_[u]
                <= nbvertices - 1);
        sprintf(name, "ctaryRankTransitivity_%i_%i", u->id_, v->id_);
        ctaryRankTransitivity[nbcons++].setName(name);
      }
    }
    model.add(ctaryRankTransitivity);
  }


  //////////////////////////////////////////////////////////////////////////////
  // Constraints that ensure that the ordering is a discretization ordering
  // every vertex has at least L references except if it is a member of the initial clique
  //////////////////////////////////////////////////////////////////////////////

  nbcons = 0;
  int nbrefcons = 0;
  IloRangeArray ctaryLrefs(env);
  //
  // in WITNESS, the constraint reflects the difference between witnesses and references
  if (this->modeltype_ == WITNESS) {
    for (Vertex* u : this->inst_->vertices_) {
      IloExpr sumwitness(env);
      for (Vertex* v : u->neighbors_) sumwitness += isbefore_[v][u];
      ctaryLrefs.add(
          sumwitness + (U - L) * (isvertexinclique_[u] - isfullref_[u]) == L);
      sprintf(name, "ctaryLRefs_%i", u->id_);
      ctaryLrefs[nbrefcons++].setName(name);
      sumwitness.end();
    }
  } else {
    for (Vertex* u : this->inst_->vertices_) {
      IloExpr sumrefs(env);
      IloExpr sumcliques(env);
      for (int c : this->cliqueidslist_[u]) {
        sumcliques += isclique_[c];
      }
      for (Vertex* v : u->neighbors_) sumrefs += isbefore_[v][u];
      ctaryLrefs.add(sumrefs + U * sumcliques - (U - L) * isfullref_[u] >= L);
      sprintf(name, "ctaryLRefs_%i", u->id_);
      ctaryLrefs[nbrefcons++].setName(name);
      sumrefs.end();
      sumcliques.end();
    }
  }
  model.add(ctaryLrefs);


  if (this->modeltype_ == RANKS) {
    IloRangeArray ctaryRankOfClique(env);
    IloExprArray sumrank(env, L + 1);
    for (int k = 0; k < L + 1; k++) {
      sumrank[k] = IloExpr(env);
    }
    for (Vertex* u : this->inst_->vertices_) {
      for (int c : this->cliqueidslist_[u]) {
        for (int k = 0; k < L + 1; k++) {
          if (this->cliques_[c]->vertices_[k] == u) sumrank[k] += isclique_[c];
        }
      }
      for (int k = 0; k < L + 1; k++) {
        ctaryRankOfClique.add(hasrank_[u][k] - sumrank[k] == 0);
        sprintf(name, "ctaryRankOfClique_%i_%i", u->id_, k);
        ctaryRankOfClique[nbcons++].setName(name);
      }
    }
  }

  //////////////////////////////////////////////////////////////////////////////
  // Constraints that strengthen the formulation
  //////////////////////////////////////////////////////////////////////////////
  //
  // if we could identify cycles composed of low degree vertices add a cut
  // specifying that the vertices included in such cycles must include at least
  // as many partially referenced vertices as there are cycles
  if (this->modeltype_ == CCG) {
    IloRangeArray ctaryLowDegreeCycles(env);
    for (int i = 0; i < this->lowdegreecycles_.size(); i++) {
      std::vector<Vertex*> cycle = this->lowdegreecycles_[i];
      int cyclesize = cycle.size();
      IloExpr sumfullrefincycle(env);
      IloExpr sumcliques(env);
      for (Vertex* v : cycle) {
        sumfullrefincycle += isfullref_[v];
        for (int c : this->cliqueidslist_[v]) {
          sumcliques += isclique_[c];
        }
      }
      ctaryLowDegreeCycles.add(
          sumfullrefincycle - sumcliques
              <= cyclesize - this->nbpartialsincycle_[i]);
      sprintf(name, "ctaryLowDegreeCycles_%i", i);
      ctaryLowDegreeCycles[i].setName(name);
    }
    model.add(ctaryLowDegreeCycles);
    //
    // if we could identify cliques that contain at least one
    // partially-referenced vertex, add a constraint to enforce this;  the
    // two-cliques need not be checked
    IloRangeArray ctaryCliquesWithPartialRef(env);
    nbcons = 0;
    for (Clique* clique : this->cliqueswithpartialref_) {
      IloExpr sumfullref(env);
      IloExpr sumcliques(env);
      for (Vertex* v : clique->vertices_) {
        sumfullref += isfullref_[v];
        for (int c : this->cliqueidslist_[v]) {
          sumcliques += isclique_[c];
        }
      }
      ctaryCliquesWithPartialRef.add(
          sumfullref - sumcliques <=
          clique->nbvertices() - this->nbpartialinclique_[clique]);
      sprintf(name, "ctaryCliquesWithPartialRef_%i", nbcons);
      ctaryCliquesWithPartialRef[nbcons++].setName(name);
    }
    model.add(ctaryCliquesWithPartialRef);
  }
}
//
// Integer programming model for the search of a discretization order that
// maximizes the number of vertices with more than free references
// modeltype:
// = 0 :	variables that indicate whether a vertex is before another one for
// 	every pair of vertices
// = 1 : variables only for each edge, but no enumeration of the cliques
// = 2 :	variables only for each edge, with enumeration of the cliques

bool DiscretizationSolver::minpartialip(float timelimit) {

  IloEnv env;
  IloTimer cpuClockTotal(env);
  IloTimer cpuClockInit(env);
  cpuClockTotal.start();
  cpuClockInit.start();

  this->inst_->computeneighbors();
  //    this->preallocatelowdegreevertices();
  //
  // enumerate the potential initial cliques
  if (!this->enumeratecliques(this->inst_->L() + 1)) {
    this->isfeasible_ = false;
    return false;
  }
  this->eliminateredundantcliques();
  //
  // solve with greedy and remove every clique that cannot be completed
  if (!this->greedysolve()) {
    std::cout << "Greedy solve found the problem to be infeasible at the"
                 " time of mip warm start." << std::endl;
    return false;
  }
  if (this->modeltype_ == CCG) {
    //
    // identify clique of vertices that will contain at least one
    // partially-referenced vertex
    this->computecliquecuts(this->cliquecutsmaxsize_);
    //
    // identify cycle of vertices with degree smaller than U+1: in each
    // of these cycles, at least one vertex is partially referenced
    this->enumeratelowdegreecycles();
  }

  IloModel model(env);
  if (this->modeltype_ == VERTEXRANK) {
    this->defineminpartial_vertexrank(model);
  } else {
    this->defineminpartialip(model);
  }

  //
  // create CPLEX object, load the model and provide initial solution
  IloCplex cplex(env);
  cplex.extract(model);
  //
  if (!this->greedymipstart(cplex)) {
    this->isfeasible_ = false;
    return false;
  }
  if ((this->modeltype_ == CCG) || (this->modeltype_ == WITNESS)) {
    cplex.use(CyclesLazyConstraints(env, *(this->inst_), *this));
  }
  if (this->modeltype_ == CCG) {
    IloCplex::MIPCallbackI::NodeId nodeid;
    cplex.use(BranchOnCliqueVariables(env, *this, nodeid));
  }

  cpuClockInit.stop();

  //////////////////////////////////////////////////////////////////////////////
  // Solve the instance
  //////////////////////////////////////////////////////////////////////////////
  //
  // set CPLEX parameters
  cplex.setParam(IloCplex::MIPDisplay, 4);
  cplex.setParam(IloCplex::TiLim, timelimit - cpuClockInit.getTime());
  cplex.setParam(IloCplex::Threads, 1);


#ifdef VERBOSE
  cplex.exportModel("model.lp");
#endif
  //
  // solve the integer program
  std::cout << "\nrevorder: ----------------------------------------------------------" << std::endl;
  std::cout << "revorder: solve an integer program to find an optimal discretization order" << std::endl;
  std::cout << "revorder: time limit has been set to " << timelimit << " seconds" << std::endl;
  std::cout << "revorder: cplex log coming below\n" << std::endl;
  try {
    cplex.solve();
  } catch (IloException& e) {
    std::cerr << e << std::endl;
  }
  cpuClockTotal.stop();


  IloAlgorithm::Status status = cplex.getStatus();
  std::cout << "revorder: ----------------------------------------------------------\n" << std::endl;
  std::cout << "revorder: CPLEX solution status = " << status << std::endl;
  isfeasible_ = (status == IloAlgorithm::Feasible) || (status == IloAlgorithm::Optimal);
  isoptimal_ = (status == IloAlgorithm::Optimal);

  if (isfeasible_) {
    //
    // retrieve the information on the solution process
    objvalue_ = std::min((int) cplex.getObjValue(), objvalue_);
    relativegap_ = cplex.getMIPRelativeGap();
    totaltime_ = cpuClockTotal.getTime();
    treatednodes_ = cplex.getNnodes();
#ifdef VERBOSE
    cplex.writeSolution("solution.sol");
#endif

    std::cout << "revorder: total cpu time                    = " << cpuClockTotal.getTime() << " s" << std::endl;
    std::cout << "revorder: initialization cpu time           = " << cpuClockInit.getTime() << " s" << std::endl;
    std::cout << "revorder: number of branching nodes         = " << treatednodes_ << std::endl;
    std::cout << "revorder: value of the objective function   = " << objvalue_ << std::endl;
    std::cout << std::setprecision(1) << "revorder: value of the relative duality gap = " << 100.0 * relativegap_ << "%" << std::endl;
  } else if (!isfeasible_) {
    std::string fileInfeasible("InfeasibleModel.lp");
    cplex.exportModel(fileInfeasible.c_str());
    std::cout << "revorder: no feasible solution was found during the optimization!" << std::endl;
    std::cout << "revorder: check the file " << fileInfeasible << " to see the corresponding model" << std::endl;
    cplex.end();
    env.end();
    return false;
  }
  //
  // build a discretization order that has the same value as the solution of
  // the integer program
  objvalue_ += this->inst_->preallocatedvertices_.size();
  this->buildoptimalorder(cplex);
  std::cout << "revorder: verification: number of part. ref. vertices = " << this->inst_->nbvertices_ - this->inst_->L() - this->bestnbfullref_ << std::endl;
  std::cout << "revorder: with preallocated vertices, we get " << objvalue_ << " part. ref. vertices" << std::endl;
  // terminate the cplex objects
  cplex.end();
  env.end();

  return isfeasible_;
}

//
// Declare the variables used in the ip models

void DiscretizationSolver::declareipvariables(IloModel & model) {

  IloEnv env = model.getEnv();
  char name[50];
  int nbvertices = this->inst_->nbvertices_;
  int L = this->inst_->L();
  int nbcliques = this->cliques_.size();

  //
  // variables that represent the order relations between the vertices
  // - isbefore_[i,j] = 1 if vertex i is before j in the order
  for (Vertex* u : this->inst_->vertices_) {
    for (Vertex* v : this->inst_->vertices_) {
      if (u == v) continue;
      isbefore_[u][v] = IloBoolVar(env);
      sprintf(name, "isbefore_%i_%i", u->id_, v->id_);
      isbefore_[u][v].setName(name);
    }
  }
  //
  // isfullref_[v] =1 if vertex v is fully-referenced and 0 otherwise
  for (Vertex* v : this->inst_->vertices_) {
    isfullref_[v] = IloBoolVar(env);
    sprintf(name, "isfullref_%i", v->id_);
    isfullref_[v].setName(name);
  }
  //
  // variables that set the initial clique
  // - variables that set the initial clique from the enumeration of the cliques
  isclique_ = IloBoolVarArray(env, nbcliques);
  for (int c = 0; c < nbcliques; c++) {
    sprintf(name, "isclique_%i", c);
    isclique_[c].setName(name);
  }
  // - when no enumeration, isvertexinclique_[v]=1 if v is in the initial clique, 0 otherwise
  for (Vertex* v : this->inst_->vertices_) {
    isvertexinclique_[v] = IloBoolVar(env);
    sprintf(name, "isvertexinclique_%i", v->id_);
    isvertexinclique_[v].setName(name);
  }

  //
  // variables that set the rank of each vertex in the order
  // - hasrank_[v,k] = 1 if vertex i has rank k in the discretization order
  //  in this model, we use these variables only for the first L vertices
  //  of the cliques (if modeltype <= 1)
  int nbranks = (this->modeltype_ == RANKS) ? nbvertices : L + 1;
  for (Vertex* v : this->inst_->vertices_) {
    hasrank_[v] = IloBoolVarArray(env, nbranks);
    for (int k = 0; k < nbranks; k++) {
      sprintf(name, "hasrank_%i_%i", v->id_, k);
      hasrank_[v][k].setName(name);
    }
  }
  //
  // - rank[v] = integer variable representing the rank of v in the order
  if (this->modeltype_ == RANKS) {
    for (Vertex* v : this->inst_->vertices_) {

      rank_[v] = IloIntVar(env, 0, nbvertices - 1);
      sprintf(name, "rank_%i", v->id_);
      rank_[v].setName(name);
    }
  }
}
//
// create the cplex model for the relaxation of the IP

void DiscretizationSolver::createrelaxationmodel(IloModel& model, IloModel & relax) {
  IloEnv env = model.getEnv();
  relax.add(model);
  for (Vertex* u : this->inst_->vertices_) {
    relax.add(IloConversion(env, isfullref_[u], ILOFLOAT));
    if (this->modeltype_ == CCG || this->modeltype_ == WITNESS || this->modeltype_ == RANKS) {
      for (Vertex* v : u->neighbors_) {
        if (v == u) continue;
        relax.add(IloConversion(env, isbefore_[u][v], ILOFLOAT));
      }
    } else {
      for (Vertex* v : this->inst_->vertices_) {
        if (v == u) continue;
        relax.add(IloConversion(env, isbefore_[u][v], ILOFLOAT));
      }
    }
    for (int k = 0; k < this->inst_->L()+1; k++) {
      relax.add(IloConversion(env, hasrank_[u][k], ILOFLOAT));
    }
    if (this->modeltype_ == RANKS) {
      for (int k = this->inst_->L()+1; k < this->inst_->nbvertices_; k++) {
        relax.add(IloConversion(env, hasrank_[u][k], ILOFLOAT));
      }
    }
  }
  for (int n = 0; n < this->cliques_.size(); n++) {

    relax.add(IloConversion(env, isclique_[n], ILOFLOAT));
  }
}
//
// Solve the linear relaxation of the input model

void DiscretizationSolver::solverelaxation(IloModel & model) {
  //
  // solve the relaxation alone first and disable every generation of cuts and
  // presolve of the integer programming problem

  IloEnv env = model.getEnv();
  IloModel relax(env);
  createrelaxationmodel(model, relax);
  IloCplex cplexrelax(env);

  cplexrelax.extract(relax);
  //
  // run cplex and retrieve the objective value
  std::cout << "\nrevorder: ----------------------------------------------------------" << std::endl;
  std::cout << "revorder: solve the relaxation of the discretization order problem" << std::endl;
  std::cout << "revorder: cplex log coming below\n" << std::endl;
  IloTimer cpuClock(env);
  cpuClock.start();
  cplexrelax.solve();
  cpuClock.stop();
  objvaluerelax_ = cplexrelax.getObjValue();
  std::cout << "revorder: value of the linear relaxation  = " << objvaluerelax_ << std::endl;
  std::cout << "revorder: total cplex cpu (relaxation) = " << cpuClock.getTime() << " s" << std::endl;
#ifdef VERBOSE
  cplexrelax.writeSolution("solutionrelax.sol");
#endif
  //
  cplexrelax.end();
}
//
// After solving the problem, build an order that produces the same objective
// value as the optimal solution

void DiscretizationSolver::buildoptimalorder(const IloCplex & cplex) {
  int nbvertices = this->inst_->nbvertices_;
  //
  // initialize the data relative to best solution
  this->bestfullref_.clear();
  for (Vertex* v : this->inst_->vertices_) {
    this->bestnbfullref_ = 0;
    this->bestnbrefs_[v] = 0;
    this->bestisfullref_[v] = false;
    this->bestrank_[v] = -1;
  }
  //
  // Compute a topological ordering of the graph
  //
  // generate the adjacency lists of the vertices corresponding to the current
  // cplex solution
  if (this->modeltype_ != VERTEXRANK) {
    float tolerance = 1e-06;
    std::map<Vertex*, std::vector<Vertex*> > adjlist;
    for (Vertex* v : this->inst_->vertices_) {
      adjlist[v] = std::vector<Vertex*>();
    }

    for (Vertex* u : this->inst_->vertices_) {
      for (Vertex* v : u->neighbors_) {
        if (cplex.getValue(this->isbefore_[u][v]) >= 1 - tolerance) {
          if ((this->modeltype_ == WITNESS) && (cplex.getValue(this->isvertexinclique(v)) >= 1 - tolerance)) {
            if (cplex.getValue(this->isvertexinclique(u)) >= 1 - tolerance) {
              if (u->id_ < v->id_) {
                adjlist[u].push_back(v);
                this->bestnbrefs_[v]++;
              }
            }
          } else {
            adjlist[u].push_back(v);
            this->bestnbrefs_[v]++;
          }
        }
      }
    }
    //
    // search for the root of the digraph
    Vertex* root = nullptr;
    int minnbrefs = this->inst_->nbvertices_;
    for (Vertex* v : this->inst_->vertices_) {
      if (this->bestnbrefs_[v] < minnbrefs) {
        minnbrefs = this->bestnbrefs_[v];
        root = v;
        if (minnbrefs == 0) break;
      }
    }
    //
    // call the enumeration of cycles, returns false if there are none
    std::vector<std::vector<Vertex*> > cycles;
    // Search for a topological order that will be valid only if the
    // digraph is acyclic
    std::vector<Vertex*> reverseorder;
    std::map<Vertex*, int> rank;
    this->topologicalorder(root, adjlist, reverseorder, rank);

    // determine whether the digraph is cyclic or not
    //
    std::vector<std::pair<Vertex*, Vertex*> > reverseedges;
    bool iscyclic = this->getreverseedges(adjlist, reverseorder, rank, reverseedges);
    if (iscyclic) {
      std::cout << "revorder: error: the final integer solution is cyclic" << std::endl;
      throw;
    }


    // modify the rank attributes of the vertices to represent their rank in the order
    for (Vertex* v : this->inst_->vertices_) {
      this->bestrank_[v] = rank[v];
      if (this->bestnbrefs_[v] >= this->inst_->U()) {
        this->bestisfullref_[v] = true;
        this->bestfullref_.push_back(v);
        this->bestnbfullref_++;
      }
    }
  } else {
    // get the rank of each vertex in the order
    for (Vertex* u : this->inst_->vertices_) {
      for (int k = 0; k < nbvertices; k++) {
        if (cplex.getValue(this->hasrank_[u][k]) >= 1 - 1.0e-6) {
          this->bestrank_[u] = k;
        }
      }
    }
    // get the number of references of each vertex
    for (Vertex* u : this->inst_->vertices_) {
      for (Vertex* v : u->neighbors_) {
        if (v->id_ <= u->id_) continue;
        if (this->bestrank_[v] < this->bestrank_[u]) this->bestnbrefs_[u]++;
        else this->bestnbrefs_[v]++;
      }
    }
    // get fully referenced vertices
    for (Vertex* u : this->inst_->vertices_) {
      if (this->bestnbrefs_[u] >= this->inst_->U()) {
        this->bestisfullref_[u] = true;
        this->bestfullref_.push_back(u);
        this->bestnbfullref_++;
      }
    }
  }
  //
  // verify that the order is indeed a referenced order
  this->reconstructsolution();
  // this->verifyorder(this->bestrank_);
  //
  // sort the input ranks map by ascening order of values
  std::set<std::pair<Vertex*, int>, Comparator> orderedranks(
      this->bestrank_.begin(), this->bestrank_.end(), valuecompare);
}