/******************************************************************************************** Name: DiscretizationSolver Discrete optimization for graph discretization Author: J.Omer Sources: C++ License: GNU General Public License v.2 History: *********************************************************************************************/ #include "bbsolver.hpp" #include <algorithm> ILOSTLBEGIN // ILOLAZYCONSTRAINTCALLBACK2(CyclesLazyConstraintsBB, 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.U(); 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; 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; } /*************************************************************************************************************************************************************/ // // sort the nodes by ascending number of ordered vertices bool bbnodesnborderedcompare(BBNode* n1, BBNode* n2) { return (n1->nbordered() < n2->nbordered()); } bool bbnodesbestcompare(BBNode*n1, BBNode* n2) { return (static_cast<float>(n1->cost_) / n1->nbordered() > static_cast<float>(n2->cost_) / n2->nbordered()); } // Public methods // // constructor and destructor // // constructor used only for the root BBNode::BBNode(Instance& inst): depth_(0), inst_(&inst), nbordered_(0), nbfullref_(0), istreated_(true) {} // // constructor used only for the initial cliques BBNode::BBNode(Instance& inst, BBNode* root, Clique& c, bool branchonsmallerindex): depth_(1), father_(root), inst_(&inst), nbordered_(0), nbfullref_(0), istreated_(false) { this->orderedvertices_.clear(); for (Vertex* v : inst.vertices_) { this->rank_[v] = -1; this->nbrefs_[v] = 0; this->isfullref_[v] = false; this->isordered_[v] = false; this->ispotentialchild_[v] = false; this->mustbefullref_[v] = false; this->maxrefs_[v] = v->degree_; } int currentrank = 1; for (Vertex* u : c.vertices_) { this->rank_[u] = currentrank++; this->orderedvertices_.push_back(u); this->switchtoordered(u); this->nbordered_++; // // update the number of references of the neighbors of the vertex for (Vertex* v : u->neighbors_) { if (!this->isordered_[v]) { this->nbrefs_[v]++; } } } // record the list of partially and fully referenced (unordered) vertices for (Vertex* v : inst.vertices_) { if (!this->isordered_[v]) { // record the list of unordered fully-referenced vertices if (this->nbrefs_[v] >= inst.U()) { this->isfullref_[v] = true; this->fullrefs_.push_back(v); } else if (this->nbrefs_[v] >= inst.L()) { // add partially referenced vertices to the list of potential choices // for branching if (branchonsmallerindex) { bool largerindex = true; Vertex* u = c.vertices_.back(); if (!u->isneighbor(v)) { if (u->id_ >= v->id_) largerindex = false; } // // to break symmetries, make sure that the chosen potential child // is always that with biggest index if (largerindex) { this->ispotentialchild_[v] = true; this->potentialchildren_.push_back(v); // // partially referenced vertices with less than U neighbors can // be ordered right away since they will never be fully referenced if (this->maxrefs_[v] <= inst.U() - 1) { this->readytoorder_.push_back(v); #ifdef VERBOSE std::cout << "revorder: bb: order vertex " << v->id_ << " without branching, maximum nb of references = " << this->maxrefs_[v] << std::endl; #endif } } else { this->mustbefullref_[v] = true; // if a vertex must be fully-referenced, but it has exactly U // neighbors, it will for sure come after all its neighbors in // the order if (this->maxrefs_[v] <= inst.U()) { for (Vertex* u : v->neighbors_) { if (this->mustbefullref_[u]) { if (this->maxrefs_[u] >= inst.U()+1) { this->maxrefs_[u]--; } } else { this->maxrefs_[u]--; } } } } } else { this->ispotentialchild_[v] = true; this->potentialchildren_.push_back(v); // partially referenced vertices with less than U neighbors can be // ordered right away since they will never be fully referenced if (this->maxrefs_[v] <= inst.U() - 1) { this->readytoorder_.push_back(v); #ifdef VERBOSE std::cout << "revorder: bb: order vertex " << v->id_ << " without branching, maximum nb of references = " << this->maxrefs_[v] << std::endl; #endif } } } } } this->dualbound_ = -1; root->addtochildren(this); } // Make a copy of the input BB node: used for every other node BBNode::BBNode(BBNode& node): id_(node.id_), depth_(node.depth_+1), father_(&node), inst_(node.inst_), nbordered_(node.nbordered_), nbfullref_(node.nbfullref_), istreated_(false) { cost_ = node.cost_; dualbound_ = node.dualbound_; primalbound_ = node.primalbound_; for (Vertex* v : inst_->vertices_) { rank_[v] = node.rank_.at(v); isordered_[v] = node.isordered_.at(v); nbrefs_[v] = node.nbrefs_.at(v); isfullref_[v] = node.isfullref_.at(v); ispotentialchild_[v] = node.ispotentialchild_.at(v); mustbefullref_[v] = node.mustbefullref_.at(v); maxrefs_[v] = node.maxrefs_.at(v); } fullrefs_.clear(); orderedvertices_.clear(); potentialchildren_.clear(); children_.clear(); for (Vertex* v : node.readytoorder_) readytoorder_.push_back(v); for (Vertex* v : node.fullrefs_) fullrefs_.push_back(v); for (Vertex* v : node.orderedvertices_) orderedvertices_.push_back(v); for (Vertex* v : node.potentialchildren_) potentialchildren_.push_back(v); } // // Destroy the node BBNode::~BBNode() { } // // remove a vertex from the list of potential choice for branching void BBNode::removefrompotentialchildren(Vertex* v) { // // no need to proceed if the vertex is not marked as potential child if (!this->ispotentialchild_[v]) return; this->ispotentialchild_[v] = false; std::vector<Vertex*>::iterator itv = this->potentialchildren_.begin(); while (itv < this->potentialchildren_.end()) { if (*itv == v) { this->potentialchildren_.erase(itv); break; } else { itv++; // detect error if the vertex was not found in the list of potential // children if (itv == this->potentialchildren_.end()) { std::cout << "revorder: bb: did not find a potential child in the list" << '\n'; throw; } } } itv = this->readytoorder_.begin(); while (itv < this->readytoorder_.end()) { if (*itv == v) { this->readytoorder_.erase(itv); break; } itv++; } } // // assign the next available rank to the input vertex void BBNode::assignnextrank(Vertex* u) { // // assign the next available rank for (Vertex* v : this->orderedvertices_) { if (u == v) { std::cout << "Les ennuis commencent ici." << std::endl; } } this->rank_[u] = this->nbordered_ + 1; this->orderedvertices_.push_back(u); this->switchtoordered(u); this->nbordered_++; this->removefrompotentialchildren(u); if (this->nbrefs_[u] <= this->inst_->L() - 1) { std::cout << "revorder: bb: trying to assign a rank to a vertex with less" " than L references" << '\n'; throw; } else if (this->nbrefs_[u] >= this->inst_->U()) { this->nbfullref_++; } // update the number of references of the neighbors of v for (Vertex* v : u->neighbors_) { if (this->rank_[v] < 0) { this->nbrefs_[v]++; // add partially-referenced vertices to the list of potential choices // for branching if (this->nbrefs_[v] == this->inst_->L()) { this->potentialchildren_.push_back(v); this->ispotentialchild_[v] = true; // partially referenced vertices with less than U neighbors can be // ordered right away since they will never be fully referenced if (this->maxrefs_[v] <= this->inst_->U() - 1) { this->readytoorder_.push_back(v); #ifdef VERBOSE std::cout << "revorder: bb: order vertex " << v->id_ << " without branching, maximum nb of references = " << this->maxrefs_[v] << std::endl; #endif } } // a vertex is fully-referenced if it has more than U references, and it // is not a potential choice for branching anymore if (this->nbrefs_[v] == this->inst_->U()) { this->isfullref_[v] = true; this->fullrefs_.push_back(v); if (this->ispotentialchild_[v]) this->removefrompotentialchildren(v); } } } } // get the current objective value at a node, depending on the ordered vertices void BBNode::setnodecost(Problem pb) { int nbpartials = 0; for (Vertex* u : this->inst_->vertices_) { if (this->isordered_[u]) { if (this->nbrefs_[u] <= this->inst_->U() - 1) nbpartials += 1; } } this->cost_ = nbpartials; } // a bb node n1 dominates another one, n2, if the corresponding partial // solution has a smaller or equal cost but its list of ordered vertices // contains that of n2 bool BBSolver::dominates(BBNode& n1, BBNode& n2) { if ( n2.cost_ < n1.cost_ ) { return false; } for (Vertex* v : n2.orderedvertices_) { if (!(n1.isordered(v))) { return false; } } return true; } // // propagate the set of ordered vertices by iteratively ordering // fully-referenced vertices void BBNode::prepropagation() { #ifdef VERBOSE cout << "prepropagation" << endl; #endif // iteratively propagate while there are fully-referenced vertices while (!this->fullrefs_.empty()) { Vertex* u = this->fullrefs_.back(); this->fullrefs_.pop_back(); this->isfullref_[u] = false; this->assignnextrank(u); } this->setnodecost(MINPARTIAL); } // // propagate the set of ordered vertices by iteratively ordering vertices // as long as there is either at least one unordered vertex with more than U // references or exactly one unordered vertex with exactly U references void BBNode::postpropagation() { #ifdef VERBOSE cout << "in postpropagation" << endl; #endif // // no need of propagation if all the vertices are ordered if (this->nbordered_ == this->inst_->nbvertices_) return; // // iteratively propagate while there are fully-referenced vertices or // exactly one partially-referenced vertex while (true) { bool ispropagate = false; Vertex* u; if (!this->fullrefs_.empty()) { // add a vertex with more than U references u = this->fullrefs_.back(); this->fullrefs_.pop_back(); this->isfullref_[u] = false; ispropagate = true; } else if (!this->readytoorder_.empty()) { // if there is a partially referenced vertex with less than U // neighbors, it can be ordered right away: it will never be fully // referenced u = this->readytoorder_.back(); this->readytoorder_.pop_back(); ispropagate = true; } else if (this->potentialchildren_.size() == 1) { // if there is exactly one partially referenced vertex: add it to the // order u = this->potentialchildren_.back(); this->potentialchildren_.pop_back(); this->ispotentialchild_[u] = false; ispropagate = true; } if (!ispropagate) break; // // add the identified vertex at the end of the order this->assignnextrank(u); } // // update the cost of the node this->setnodecost(MINPARTIAL); } /******************************************************************************* Class BBSolver - an instance of BBSolver is a solver of the discretization problem using branch-and-bound *******************************************************************************/ // Public methods // // constructor and destructor BBSolver::BBSolver(Instance* inst, Problem pb, std::string optionfile, float timelimit): DiscretizationSolver(inst, BRANCHANDBOUND, pb, NONE, optionfile, timelimit) { this->isfeasible_ = false; this->isoptimal_ = false; this->bbnodes_ = 1; this->treatednodes_ = 0; this->nbactivenodes_ = 0; this->primalbound_ = 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); std::string optionname; char buf[100]; int optionvalue; std::string optionstring; //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("relaxmodel") != std::string::npos) { if (!(iss >> buf)){ std::cerr << "revorder: error: there is a mistake with the format " "of the option file" <<std::endl; throw; } optionstring = std::string(buf); if (!strcmp(optionstring.c_str(),"ccg")) { this->modeltype_ = CCG; } else if (!strcmp(optionstring.c_str(),"cycles")) { this->modeltype_ = CYCLES; } else if (!strcmp(optionstring.c_str(),"ranks")) { this->modeltype_ = RANKS; } else if (!strcmp(optionstring.c_str(),"vertexrank")) { this->modeltype_ = VERTEXRANK; } else if (!strcmp(optionstring.c_str(),"witness")) { this->modeltype_ = WITNESS; } continue; } if (!(iss >> optionvalue)){ std::cerr << "revorder: error: there is a mistake with the format of " "the option file" <<std::endl; throw; } // error // if (optionname.find("branchonsmallerindex") !=std::string::npos) { branchonsmallerindex_ = (bool) optionvalue; continue; } else if (optionname.find("prunesameneighbors") !=std::string::npos) { prunesameneighbors_ = (bool) optionvalue; continue; } else if (optionname.find("checkdominance") !=std::string::npos) { if (optionname.find("checkdominancealltree") !=std::string::npos) { checkdominancealltree_ = (bool) optionvalue; continue; } checkdominance_ = (bool) optionvalue; continue; } else if (optionname.find("maxdeltadominance") !=std::string::npos) { maxdeltadominance_ = optionvalue; continue; } else if (optionname.find("improvedualbound") !=std::string::npos) { improvedualbound_ = (bool) optionvalue; continue; } else if (optionname.find("userelaxbound") !=std::string::npos) { userelaxbound_ = (bool) optionvalue; continue; } else if (optionname.find("explorebest") !=std::string::npos) { explorebest_ = (bool) optionvalue; continue; } else if (optionname.find("exploredepth") !=std::string::npos) { if (optionname.find("exploredepthbeforebest") !=std::string::npos) { exploredepthbeforebest_ = (bool) optionvalue; continue; } exploredepth_ = (bool) optionvalue; continue; } } } else { // // set some parameters for the solution of the relaxation in dual bounds // computations this->modeltype_ = CCG; this->branchonsmallerindex_ = true; this->prunesameneighbors_ = true; this->checkdominance_ = true; this->checkdominancealltree_ = true; this->maxdeltadominance_ = 1; this->improvedualbound_ = false; this->userelaxbound_ = false; this->explorebest_ = false; this->exploredepth_ = false; this->exploredepthbeforebest_ = true; } // // build a root node this->rootnode_ = new BBNode(*inst); } // BBSolver::~BBSolver() { cplexrelax_.end(); relax_.end(); env_.end(); cplexdual_.end(); modeldual_.end(); envdual_.end(); delete rootnode_; } // // main method : called to run the branch-and-bound algorithm int BBSolver::solve() { // // run timer IloTimer cpuClockTotal(this->env_); IloTimer cpuClockInit(this->env_); cpuClockTotal.start(); cpuClockInit.start(); std::cout << std::endl; std::cout << "revorder: " "----------------------------------------------------------" << std::endl; std::cout << "\nrevorder: bb: Run preprocessing procedures " << std::endl << std::endl; // // try and reduce the size of the instance right from the beginnning this->inst_->computeneighbors(); this->preallocatelowdegreevertices(); // // enumerate the potential initial cliques if (!this->enumeratecliques(this->inst_->L() + 1)) { this->isfeasible_ = false; return false; } // // eliminate the redundant and dominated cliques this->eliminateredundantcliques(); // // solve with greedy and remove cliques that cannot be completed this->isfeasible_ = this->greedysolve(); if (!this->isfeasible_) return false; // // initialize the clique nodes for (Clique* c : this->cliques_) { BBNode* cliquenode =new BBNode(*inst_, rootnode_, *c, this->branchonsmallerindex_); this->bbnodesqueue_.push_back(cliquenode); cliquenode->primalbound_ = c->greedyobjvalue_; cliquenode->setid(this->bbnodes_); cliquenode->setnodecost(this->problem_); cliquenode->prepropagation(); cliquenode->postpropagation(); this->activenodes_[cliquenode->cost_].push_back(cliquenode); this->nbactivenodes_++; // // first propagate the partial order this->bbnodes_++; } std::cout << "revorder: bb: preprocessing created " << this->bbnodesqueue_.size() << " initial clique nodes" << std::endl; // // sort the bb nodes according to the choice of exploration method if (this->exploredepth_ || this->exploredepthbeforebest_) { std::make_heap(this->bbnodesqueue_.begin(), this->bbnodesqueue_.end(), bbnodesnborderedcompare); } else if (this->explorebest_) { std::make_heap(this->bbnodesqueue_.begin(), this->bbnodesqueue_.end(), bbnodesbestcompare); } else { std::cout << "revorder: bb: need to choose an exploration method in " "branch-and-bound" << std::endl; throw; } // #ifdef VERBOSE std::cout << "revorder: bb: greatest number of ordered vertices in a clique: "; std::cout << ", " << this->bbnodesqueue_.front()->nbordered() << " vertices" << std::endl; #endif // // compute sets of vertices that must 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(); // // initialize the IP used for improved dual bound this->initializedualboundIP(); // // set greedy solution as initial primal bound this->primalbound_ = this->objvalue_; // cpuClockInit.stop(); // Run the branch-and-bound algorithm // // initialize the ip model to find dual bounds using the linear relaxation // IloModel model(this->env_); this->relax_ = IloModel(this->env_); // this->defineminpartialip(model); if (userelaxbound_) { // this->createrelaxationmodel(model, this->relax_); this->defineminpartialip(this->relax_); this->cplexrelax_ = IloCplex(this->env_); this->cplexrelax_.extract(this->relax_); } // // treat std::cout << std::endl; std::cout << "revorder: " "----------------------------------------------------------" << std::endl; std::cout << "\nrevorder: bb: Start the B&B algorithm " << std::endl << std::endl; while (!this->bbnodesqueue_.empty()) { this->treatnode(*(this->bbnodesqueue_.front())); if (cpuClockTotal.getTime() > this->timelimit_) { std::cout << "revorder: bb: time limit has been reached, stop the " "solution algorithm" << std::endl; break; } } cpuClockTotal.stop(); // Display the results this->totaltime_ = cpuClockTotal.getTime(); this->relaxtime_ = 0.0; std::cout << std::endl; std::cout << "revorder: " "----------------------------------------------------------" << std::endl; std::cout << "revorder: " "----------------------------------------------------------" << std::endl; std::cout << "\nmdjeep: Branch-and-bound solution report: " << std::endl; if (isfeasible_) { this->objvalue_ = this->primalbound_; // the solution is optimal only if all the bb nodes have been treated if (this->bbnodesqueue_.empty()) { isoptimal_ = true; std::cout << "revorder: the instance was solved to optimality" << std::endl; } else { std::cout << "revorder: the branch-and-bound was stopped before proving" " optimality" << std::endl; } // reconstruct the solution by including the preallocated vertices objvalue_ += this->inst_->preallocatedvertices_.size(); this->reconstructsolution(); // // display the solution 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 explored nodes = " << this->treatednodes_ << std::endl; std::cout << "revorder: number of created nodes = " << this->bbnodes_ << std::endl; std::cout << "revorder: value of the objective function = " << this->objvalue_ << " (after addition of preallocated vertices)" << std::endl; // // verify the resulting order is indeed a revorder this->verifyorder(this->bestrank_); } else { std::cout << "revorder: the instance is not discretizable!" << std::endl; } return isfeasible_; } // // treat a branch-and-bound node (compute bounds and branch) void BBSolver::treatnode(BBNode& node) { this->treatednodes_++; if (std::remainder(this->treatednodes_, 100) == 0) { std::cout << "revorder: bb: treated nodes = " << this->treatednodes_ << ", nodes in the queue = " << this->bbnodesqueue_.size() << ", nodes in memory = " << this->nbactivenodes_ << ", primal bound = " << this->primalbound_ << std::endl; } // // remove the bb node from the list // // make sure to maintain the heap first if (this->isfeasible_) { if (this->exploredepth_) { std::pop_heap(this->bbnodesqueue_.begin(), this->bbnodesqueue_.end(), bbnodesnborderedcompare); } else if (this->explorebest_ || this->exploredepthbeforebest_) { std::pop_heap(this->bbnodesqueue_.begin(), this->bbnodesqueue_.end(), bbnodesbestcompare); } } else { if (this->explorebest_) { std::pop_heap(this->bbnodesqueue_.begin(), this->bbnodesqueue_.end(), bbnodesbestcompare); } else { std::pop_heap(this->bbnodesqueue_.begin(), this->bbnodesqueue_.end(), bbnodesnborderedcompare); } } // actually remove the node from the queue this->bbnodesqueue_.pop_back(); // update the primal bound and prune the node if it is a leaf of the // enumeration tree if (node.nbordered() == this->inst_->nbvertices_) { #ifdef VERBOSE std::cout << "revorder: bb: reached a leaf at node " << node.id() << ": depth = " << node.depth(); #endif node.primalbound_ = this->getobjvalue(node.rank_, node.nbrefs_); // // update the primal bound if improved if (node.primalbound_ < this->primalbound_) { // // swap the search method if this is the first feasible solution std::cout << "revorder: bb: the primal bound has been improved, value = " << node.primalbound_ << std::endl; if (!this->isfeasible_) { if (this->exploredepthbeforebest_) { std::make_heap(this->bbnodesqueue_.begin(), this->bbnodesqueue_.end(), bbnodesbestcompare); } this->isfeasible_ = true; } this->primalbound_ = node.primalbound_; this->bestnbfullref_ = this->getnbfullref(node.rank_, node.nbrefs_); for (Vertex* v : inst_->vertices_) { this->bestrank_[v] = node.rank(v); } } this->erasenodefromall(&node); } else { // otherwise, compute dual and primal bound and prepare for next node // prune the node if no potential child node if (node.potentialchildren_.empty()) { // #ifdef VERBOSE std::cout << "revorder: bb: prune node " << node.id() << ": "; std::cout << ": no feasible solution on this branch" << std::endl; #endif // this->erasenodefromall(&node); } else { // compute a dual bound based on the ordered vertices this->computedualbound(node); // // prune the node by using bounds if (node.dualbound_ >= this->primalbound_) { #ifdef VERBOSE std::cout << "revorder: bb: prune node " << node.id() << ": "; std::cout << "using bounds: primal = " << this->primalbound_; std::cout << " ; dual = " << node.dualbound_ << std::endl; #endif this->erasenodefromall(&node); } else { // otherwise, create new nodes by branching this->branch(node); node.istreated_ = true; // if (!this->checkdominancealltree_) { // this->erasenodefromall(&node); // } } } } // // treat next node in the queue // since it is a heap, the front node is always the greatest in the // implemented order #ifdef VERBOSE if (!this->bbnodesqueue_.empty()) { BBNode* nextnode = this->bbnodesqueue_.front(); std::cout << '\n' << "revorder: bb: treat node " << nextnode->id() << " ; depth = " << nextnode->depth() << " ; "; std::cout << nextnode->nbordered() << " ordered vertices ; " << nextnode->nbpartialref() << " partially referenced vertices" << std::endl; } #endif } // create new nodes by branching void BBSolver::branch(BBNode& node) { // create the children of the input node // propagate the order starting from each incomplete order to detect // dominatedorders std::vector<BBNode*> childnodes; std::map<BBNode*, bool> isdominated; std::map<Vertex*, bool> issymmetric; // // we start by checking diverse pruning rules that need to be applied // before dominance for (int i = 0; i < node.potentialchildren_.size() ; i++) { Vertex* u = node.potentialchildren_[i]; issymmetric[u] = false; if (!this->prunesameneighbors_) continue; for (int j = 0; j < i; j++) { Vertex* v = node.potentialchildren_[j]; if (issymmetric[v]) continue; // if two vertices that can be chosen for branching have the exact same // unordered neighbors, only that with smaller number of references // needs to be considered for branching at this stage bool sameneighbors = true; for (Vertex* neighbor : u->neighbors_) { if (!node.isordered(neighbor)) { if ( !v->isneighbor_[neighbor] ) { sameneighbors = false; break; } } } if (sameneighbors) { #ifdef VERBOSE std::cout << "revorder: bb: prune node: two potential children have same neighbors: vertices " << u->id_ << " and " << v->id_ << std::endl; #endif if (node.nbrefs_[u] < node.nbrefs_[v]) { issymmetric[v] = true; } else if (node.nbrefs_[v] < node.nbrefs_[u]) { issymmetric[u] = true; } else { if (u->id_ < v->id_) issymmetric[v] = true; else if (v->id_ < u->id_) issymmetric[u] =true; } } } } for (Vertex* v : node.potentialchildren_) { if (issymmetric[v]) continue; // do not consider the vertices that are // symmetric BBNode* child = new BBNode(node); // // in MINPARTIAL, we can always make the arbitrary decision that if a // vertex must be ordered when it is still partially referenced, then it // is the vertex with smallest index among all the potential children; as // a consequence the vertices with smaller index must be removed from the // list of potential children if ( this->branchonsmallerindex_ ) { std::vector<Vertex*>::iterator itv = child->potentialchildren_.begin(); while (itv < child->potentialchildren_.end()) { if ( ((*itv)->id_ < v->id_) ) { child->ispotentialchild_[*itv] = false; child->potentialchildren_.erase(itv); child->mustbefullref_[*itv] = true; // // if a vertex must be fully-referenced, but it has exactly U // neighbors, it will for sure come after all its neighbors in the // order if (child->maxrefs_[*itv] <= this->inst_->U()) { for (Vertex* u : v->neighbors_) { if (child->mustbefullref_[u]) { if (child->maxrefs_[u] >= this->inst_->U()+1) { child->maxrefs_[u]--; } } else { child->maxrefs_[u]--; } } } } else { itv++; } } } child->assignnextrank(v); child->prepropagation(); isdominated[child] = false; childnodes.push_back(child); } // // check for classical dominance for (BBNode* n1 : childnodes) { if (!this->checkdominance_) continue; // do not check dominance if option // is deactivated if (isdominated[n1]) continue; for (BBNode* n2 : childnodes) { if (n2 == n1) { continue; } else if (isdominated[n2]) { continue; } else if (this->dominates(*n2, *n1)) { // classical domination isdominated[n1] = true; // #ifdef VERBOSE std::cout << "revorder: bb: prune node: dominance among children" << std::endl; #endif break; } else if (this->dominates(*n1, *n2)) { isdominated[n2] = true; // #ifdef VERBOSE std::cout << "revorder: bb: prune node: dominance among children" << std::endl; #endif } } } // // scan all the bb nodes in the queue to delete the dominated ones if // option is activated if ((this->checkdominancealltree_) && (this->checkdominance_)) { for (BBNode* n : childnodes) { if (isdominated[n]) continue; for (int deltacost = -this->maxdeltadominance_ ; deltacost <= this->maxdeltadominance_; deltacost++) { if (n->cost_ <= deltacost) continue; if (!isdominated[n]) { isdominated[n] = checkdominancewithlist(n, this->activenodes_[n->cost_ - deltacost]); } } } } // // delete the redundant nodes and add the others to the node queue for (BBNode* n : childnodes) { if (isdominated[n]) { delete n; } else { // make sure to maintain the heap while pushing the new node in the queue this->bbnodes_++; n->setid(this->bbnodes_); node.addtochildren(n); n->postpropagation(); this->bbnodesqueue_.push_back(n); this->activenodes_[n->cost_].push_back(n); this->nbactivenodes_++; if (this->exploredepth_) { std::push_heap(this->bbnodesqueue_.begin(), this->bbnodesqueue_.end(), bbnodesnborderedcompare); } else if (this->explorebest_) { std::push_heap(this->bbnodesqueue_.begin(), this->bbnodesqueue_.end(), bbnodesbestcompare); } else if (this->exploredepthbeforebest_) { if (!this->isfeasible_) { std::push_heap(this->bbnodesqueue_.begin(), this->bbnodesqueue_.end(), bbnodesnborderedcompare); } else { std::push_heap(this->bbnodesqueue_.begin(), this->bbnodesqueue_.end(), bbnodesbestcompare); } } } } #ifdef VERBOSE std::cout << "revorder: bb: " << this->bbnodesqueue_.size() << " nodes in the queue" << std::endl; #endif } // // check the dominance of input node with every node in the input vector bool BBSolver::checkdominancewithlist(BBNode* n1, std::vector<BBNode*>& nodelist) { std::vector<BBNode*>::iterator itnode = nodelist.begin(); while (itnode < nodelist.end()) { BBNode* n2 = (*itnode); if (this->dominates(*n2, *n1)) { // #ifdef VERBOSE std::cout << "revorder: bb: prune node: dominance of potential child "; if (n2->istreated_) std::cout << " with treated node" << std::endl; else std::cout << "with queue " << std::endl; std::cout << "revorder: bb: depth: dominant = " << n2->depth() << ", dominated = " << n1->depth() << std::endl; std::cout << "revorder: bb: costs: dominant = " << n2->cost_ << ", dominated = " << n1->cost_ << std::endl; #endif // return true; } else if (this->dominates(*n1, *n2)) { // #ifdef VERBOSE std::cout << "revorder: bb: prune node: dominance of created node "; if (n2->istreated_) std::cout << " already treated" << std::endl; else std::cout << "still in queue" << std::endl; std::cout << "revorder: bb: depth: dominant = " << n1->depth() << ", dominated = " << n2->depth() << std::endl; std::cout << "revorder: bb: costs: dominant = " << n1->cost_ << ", dominated = " << n2->cost_ << std::endl; #endif // erasenodefromall(*itnode); } else { itnode++; } } return false; } // // erase a node from every list where it appears void BBSolver::erasenodefromall(BBNode* node) { // first erase it from the list of children of its father for (auto itn = node->getfather()->children_.begin(); itn < node->getfather()->children_.end(); itn++) { if (*itn == node) { node->getfather()->children_.erase(itn); break; } } // then erase its descendants if any for (BBNode* child: node->children_) { this->erasealldescendants(child); } // erase from active nodes for (auto itn = activenodes_[node->cost_].begin(); itn < activenodes_[node->cost_].end(); itn++) { if (*itn == node) { activenodes_[node->cost_].erase(itn); this->nbactivenodes_--; break; } } if (!node->istreated_) { for (auto itn = bbnodesqueue_.begin(); itn < bbnodesqueue_.end(); itn++) { if (*itn == node) { bbnodesqueue_.erase(itn); break; } } } delete node; } // erase all the descendants of a node from all list of nodes and delete them void BBSolver::erasealldescendants(BBNode* node) { for (BBNode* child: node->children_) { erasealldescendants(child); } for (auto itn = activenodes_[node->cost_].begin(); itn < activenodes_[node->cost_].end(); itn++) { if (*itn == node) { activenodes_[node->cost_].erase(itn); this->nbactivenodes_--; break; } } if (!node->istreated_) { for (auto itn = bbnodesqueue_.begin(); itn < bbnodesqueue_.end(); itn++) { if (*itn == node) { bbnodesqueue_.erase(itn); break; } } } delete node; } // compute a dual bound from the partial order described in the input node double BBSolver::computedualbound(BBNode& node) { // // compute a simple bound by just looking at the ordered vertices // // first get the number of partially referenced vertices in the order int nbpartials = node.nbordered() - node.nbfullref() - this->inst_->L(); // // then aknowledge that vertices with less than U neighbors cannot be // fully-referenced; but beware that we know that one potential child will // be partially-referenced, so do not count it twice int nbpotentialpartials = 1; for (Vertex* v: this->inst_->vertices_) { if (!node.isordered(v)) { if (v->degree_ <= this->inst_->U() - 1) { if (node.ispotentialchild_[v]) { nbpartials++; nbpotentialpartials = 0; } else{ nbpartials++; } } } } // finally for each edge linking two vertices with exactly U neigbors, one // will be partially referenced; still need to beware with the potential // children of the node std::map<Vertex*,bool> inpartialedge; for (Vertex* u: this->inst_->vertices_) inpartialedge[u] = false; for (Vertex* u: this->inst_->vertices_) { if (!node.isordered(u) && (!inpartialedge[u]) && (u->degree_ == this->inst_->U())) { for (Vertex* v: u->neighbors_) { if (!node.isordered(v) && (!inpartialedge[v]) && (v->degree_ == this->inst_->U())) { nbpartials++; inpartialedge[u] = true; inpartialedge[v] = true; if (node.ispotentialchild_[v]) nbpotentialpartials = 0; break; } } } } nbpartials += nbpotentialpartials; int trivialbound = nbpartials; // // update current dual bound if improved node.dualbound_ = std::max(node.dualbound_, trivialbound); if ((!this->improvedualbound_) && (!this->userelaxbound_) ) { return node.dualbound_; } ///////////////////////////////////////////////////////////////////////////// // Improve the trivial bound by considering several cuts ///////////////////////////////////////////////////////////////////////////// if (this->improvedualbound_) { for (Vertex* v : this->inst_->vertices_) { if (node.isordered(v)) { if (node.nbrefs(v) >= this->inst_->U()) { this->ispartial_[v].setBounds(0, 0); } else { this->ispartial_[v].setBounds(1,1); } } else if (node.mustbefullref_[v]) { this->ispartial_[v].setBounds(0, 0); } else if (v->degree_ <= this->inst_->U() - 1) { this->ispartial_[v].setBounds(1,1); } else { this->ispartial_[v].setBounds(0, 1); } } // // at least one among the potential children will be partially referenced IloExpr sumpartialinpotentials(this->envdual_); for (Vertex* v : node.potentialchildren_) { sumpartialinpotentials += this->ispartial_[v]; } IloRange ctPartialsInPotentials(this->envdual_, -sumpartialinpotentials, -1); this->modeldual_.add(ctPartialsInPotentials); sumpartialinpotentials.end(); // // solve the IP this->cplexdual_.solve(); IloAlgorithm::Status statusimproved = this->cplexdual_.getStatus(); int improvedbound = this->inst_->nbvertices_; if (statusimproved==IloAlgorithm::Infeasible) { node.dualbound_ = this->inst_->nbvertices_; #ifdef VERBOSE std::cout << "revorder: bb: improved dual bound IP is infeasible: prune the node" << std::endl; #endif } else { improvedbound = ceil(this->cplexdual_.getObjValue()) - this->inst_->L(); if (improvedbound > node.dualbound_ ){ node.dualbound_ = improvedbound; } } this->modeldual_.remove(ctPartialsInPotentials); ctPartialsInPotentials.end(); #ifdef VERBOSE std::cout << "revorder: node dual bound = " << improvedbound << " ; trivialbound = " << trivialbound << std::endl; #endif } ///////////////////////////////////////////////////////////////////////////// // Compute a dual bound by solving the LP relaxation of a chosen model ///////////////////////////////////////////////////////////////////////////// // Start searching for better bounds only if trivial bound is not too far from primal bound and a significant number of nodes have been treated to improve primal bound if ( (this->userelaxbound_) && (this->treatednodes_ >= 1000) && (trivialbound * 2.0 >= this->primalbound_)) { // fix all the variables related to the vertices that already in the current incomplete order for (Vertex* v1 : this->inst_->vertices_) { if (node.rank(v1) >= this->inst_->L() + 2) { if (node.nbrefs(v1) >= this->inst_->U()) { isfullref_[v1].setBounds(1, 1); } else { isfullref_[v1].setBounds(0, 0); } } else if ((node.rank(v1) >= 1) && (node.rank(v1) <= this->inst_->L()+1)) { isfullref_[v1].setBounds(1, 1); } else { if (node.mustbefullref_[v1] == true) { isfullref_[v1].setBounds(1,1); } else { isfullref_[v1].setBounds(0, 1); } } for (Vertex* v2 : v1->neighbors_) { if ((node.rank(v1) < 0) && (node.rank(v2) < 0)) isbefore_[v1][v2].setBounds(0, 1); else if ( (node.rank(v1) < 0) && (node.rank(v2) >= 1)) isbefore_[v1][v2].setBounds(0, 0); else if ((node.rank(v1) >= 1) && (node.rank(v2) < 0)) isbefore_[v1][v2].setBounds(1, 1); else if ((node.rank(v1) >= 1) && (node.rank(v2) >= 1) && (node.rank(v1) < node.rank(v2)) ) isbefore_[v1][v2].setBounds(1, 1); else if ((node.rank(v1) >= 1) && (node.rank(v2) >= 1) && (node.rank(v1) > node.rank(v2)) ) isbefore_[v1][v2].setBounds(0, 0); else { cout << node.rank(v1) << "; " << node.rank(v2) << endl; throwError("abnormal rank values"); } } } IloExpr sumnotallfullref(this->env_); int maxnbfullref; for (Vertex* v : node.potentialchildren_) { sumnotallfullref += isfullref_[v]; } maxnbfullref = node.potentialchildren_.size() - 1; IloRange cons(this->env_, sumnotallfullref, maxnbfullref); relax_.add(cons); cplexrelax_.setParam(IloCplex::SimDisplay, 0); // cplexrelax_.setParam(IloCplex::Param::ParamDisplay, 0); cplexrelax_.setParam(IloCplex::Threads, 1); cplexrelax_.setParam(IloCplex::MIPDisplay, 0); cplexrelax_.setParam(IloCplex::TuningDisplay, 0); cplexrelax_.setOut(cplexrelax_.getEnv().getNullStream()); bool updateprimal_ = false; if ( updateprimal_ && (node.nbordered() >= 0.7 * this->inst_->nbvertices_) ) { cplexrelax_.setParam(IloCplex::NodeLim, 9223372036800000000); cplexrelax_.setParam(IloCplex::MIPSearch, 1); if ((this->modeltype_ == CCG) || (this->modeltype_ == WITNESS)) { cplexrelax_.use(CyclesLazyConstraintsBB(cplexrelax_.getEnv(), *(this->inst_), *this)); } } else { cplexrelax_.setParam(IloCplex::NodeLim, 0); } cplexrelax_.solve(); IloAlgorithm::Status status = cplexrelax_.getStatus(); // // if the relaxation is infeasible, the node can be pruned : set dual bound to negative value to state this if (status != IloAlgorithm::Infeasible) { int relaxbound = ceil(cplexrelax_.getBestObjValue()); // #ifdef VERBOSE std::cout << "revorder: bb: trivial dual bound = " << trivialbound << " ; relaxation bound = " << relaxbound; std::cout << " (best primal bound = " << this->primalbound_ << ")" << std::endl; #endif if ( updateprimal_ && (node.nbordered() >= 0.7 * this->inst_->nbvertices_) ) { if (relaxbound < this->primalbound_) { this->primalbound_ = relaxbound; std::cout << "best primal bound updated = " << this->primalbound_ << std::endl; this->bestnbfullref_ = this->inst_->nbvertices_ - this->primalbound_; 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 (cplexrelax_.getValue(this->isbefore_[u][v]) >= 1 - tolerance) { adjlist[u].push_back(v); } } } // // search for the root of the digraph Vertex* root = nullptr; for (Vertex* v : this->inst_->vertices_) { if (node.rank(v) == 1) { root = v; } } // 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; } for (Vertex* v : this->inst_->vertices_) { this->bestrank_[v] = rank[v]; } } } // // update current dual bound if improved if (relaxbound > node.dualbound_) { node.dualbound_ = relaxbound; // #ifdef VERBOSE std::cout << "revorder: bb: new dual bound = " << relaxbound << std::endl; std::cout << "revorder: bb: number of ordered vertices = " << node.nbordered() << std::endl; #endif // } } else{ node.dualbound_ = this->inst_->nbvertices_; #ifdef VERBOSE std::cout << "revorder: bb: dual bound relaxation is infeasible: prune the node" << std::endl; #endif } relax_.remove(cons); cons.end(); } return node.dualbound_; } // initialize the IP model for improved dual bound void BBSolver::initializedualboundIP() { this->modeldual_ = IloModel(this->envdual_); this->cplexdual_ = IloCplex(this->modeldual_); IloExpr obj(this->envdual_); char name[256]; for (Vertex* v : this->inst_->vertices_) { this->ispartial_[v] = IloBoolVar(this->envdual_); sprintf(name, "ispartial%i", v->id_); this->ispartial_[v].setName(name); obj += this->ispartial_[v]; } this->modeldual_.add(IloMinimize(this->envdual_, obj)); obj.end(); // // 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 for (Clique* c : this->cliqueswithpartialref_) { IloExpr sumpartialsinclique(this->envdual_); for (Vertex* v : c->vertices_) sumpartialsinclique += this->ispartial_[v]; this->modeldual_.add(sumpartialsinclique >= this->nbpartialinclique_[c]); sumpartialsinclique.end(); } // // 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 IloRangeArray ctaryLowDegreeCycles(this->envdual_); int nbcons = 0; for (int i = 0; i < this->lowdegreecycles_.size(); i++) { std::vector<Vertex*> cycle = this->lowdegreecycles_[i]; IloExpr sumpartialsincycle(this->envdual_); for (Vertex* v : cycle) { sumpartialsincycle += this->ispartial_[v]; } ctaryLowDegreeCycles.add(sumpartialsincycle >= this->nbpartialsincycle_[i]); sprintf(name, "ctaryLowDegreeCycles_%i", nbcons); ctaryLowDegreeCycles[nbcons++].setName(name); sumpartialsincycle.end(); } this->modeldual_.add(ctaryLowDegreeCycles); ctaryLowDegreeCycles.end(); // // load the integer problem and set display parameters this->cplexdual_.setParam(IloCplex::MIPDisplay, 0); this->cplexdual_.setParam(IloCplex::SimDisplay, 0); this->cplexdual_.setParam(IloCplex::TiLim, 10); this->cplexdual_.setParam(IloCplex::Threads, 1); // this->cplexdual_.setParam(IloCplex::Param::ParamDisplay, 0); }