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