Commit d035adae authored by Jérémy's avatar Jérémy
Browse files

Share source files, and update Readme

parent 5593ee62
bin/
.lastrunsuccess
*.tex
*.o
# MIN REVORDER
A C++ code for the solution of a vertex ordering optimization problem where vertices have required and desired numbers of adjacent predecessors.
\ No newline at end of file
A C++ code for the solution of a vertex ordering optimization problem, MIN REVORDER, where vertices have required and desired numbers of adjacent predecessors. The main contribution of this code is in the implementation of the branch-and-bound article described in :
"J. Omer, A. Mucherino. Referenced Vertex Ordering Problem: Theory, Applications and Solution Methods. 2020. <hal-02509522>" [1]
THe code also reproduces solutiion methods described in
"J. Omer and D. Gonçalves. An integer programming approach for the search of discretization orders in distance geometry problems. Optimization Letters. 2017." [2], and
"M. MacNeil, M. Bodur. Integer Programming, Constraint Programming, and Hybrid Decomposition Approaches to Discretizable Distance Geometry Problems. 2019. <arXiv:1907.12468>" [3]
## How to install
The code requires CPLEX 12.7 or above and the CPLEX install directory must be exported in the environment variable CPLEX_INSTALL_DIR, e.g.,
> export CPLEX_INSTALL_DIR="/Users/yourname/Applications/IBM/ILOG/CPLEX_Studio127"
Then, if using a Linux or OSX distribution, it should be sufficient to open a terminal from the local directory of this repository and type
> mkdir bin
> make
## How to use
The main function is to solve the instances described by some input files. This is done by calling, e.g.:
> ./bin/revorder -d benchmark_article/bb_vs_existent -i random* -a bb -L 3 -U 4
The above command runs the branch-and-bound algorithm with default options, L=3 and U=4 on every instance whose name starts with "random" in the directory benchmark_article/bb_vs_existent. The results are then saved in ./benchmark_article/bb_vs_existent/results_BB_2020-03-17.tex, where the end of the file name will change depending on the date.
To solve these instances by solving an integer programming (IP) formulation, two arguments must be specified. For instance, the following command
> ./bin/revorder -d benchmark_article/bb_vs_existent -i random* -a ip -f ccg -L 3 -U 4
will solve the same random instances with the cycle cut generation algorithm originally described in "J. Omer and D. Gonçalves. An integer programming approach for the search of discretization orders in distance geometry problems. Optimization Letters, 2017".
Many other usage examples can be found in the script files shared in the ./bashscipts directory. More details on input arguments can also be found by typing:
> ./bin/revorder -help
## Instances, scripts and options
All the instances used in the experiments section of [1] are included in the ./benchmark_article directory, where they are organized similarly to the presentation of the experiments provided in [1].
The scripts included in ./bashscripts can be used to reproduce the experiments.
The option values corresponding to the versions compared in [1] are given in the ./optionfiles directory. One set of options can be used when solving an instance by specifying an option files with the -o argument.
## Citing the code
If the code is useful to your research, please cite:
"J. Omer, A. Mucherino. Referenced Vertex Ordering Problem: Theory, Applications and Solution Methods. 2020. ⟨hal-02509522⟩"
\ No newline at end of file
This diff is collapsed.
/********************************************************************************************
Name: DiscretizationSolver
Discrete optimization for graph discretization
Author: J.Omer
Sources: C++
License: GNU General Public License v.2
History:
*********************************************************************************************/
#ifndef __bbsolver__
#define __bbsolver__
#include "discretizationsolver.hpp"
/*******************************************************************************
class BBNode
- an instance of BBNode is a specific node in the branch-and-bound algorithm
*******************************************************************************/
class BBNode{
private:
int id_; // unique id_ of the node
int depth_; // depth of the node in the BB tree
BBNode* father_; // father node
Instance* inst_;
std::map<Vertex*, bool> isordered_; // true if the vertex is ordered
std::vector<Vertex*> fullrefs_; // unordered vertices with more than U references
std::map<Vertex*, bool> isfullref_; // true if the vertex is not ordered and has more than U references
int nbordered_;
int nbfullref_;
public:
std::map<Vertex*, int> rank_; // rank of the ordered vertices
std::map<Vertex*, int> nbrefs_; // nb of references of each vertex (valid only if <= U)
std::vector<Vertex*> orderedvertices_; // list of the ordered vertices
std::vector<Vertex*> partialrefs_; // unordered vertices with at least L references but less than U references
std::vector<Vertex*> potentialchildren_; // list of vertices that can be chosen for branching at ths node
std::vector<Vertex*> readytoorder_; // list of vertices that can be ordered right now: for instance, vertices that have L or more references and strictly less than U neighbors
std::map<Vertex*, bool> ispotentialchild_; // false if branching on this vertex is not necessary for dominance or symmetry reasons
std::map<Vertex*, bool> mustbefullref_; // true if the vertex is forced to be ordered only when fully-referenced
std::map<Vertex*, int> maxrefs_; // maximum number of references each vertex can have among its neighbors: it reflects that some considerations ensure that a vertex must go last among its neighbors
std::vector<BBNode*> children_; // list of children nodes
int cost_; // cost of the currently ordered vertices
int dualbound_; // best dual bound_ computed at the node
int primalbound_; // primal bound_ of the node if a greedy algorithm has been run fromm its ordered vertices
bool istreated_; // true if the node has already been treated and is just kept for dominance
public:
// Public methods
//
// constructor and destructor
//
BBNode(Instance& inst); // used only for the root
BBNode(Instance& inst, BBNode* root, Clique& c, bool branchonsmallerindex); // used only for the initial cliques
BBNode(BBNode& node); // used for every other node
~BBNode();
//
// access and set methods
inline int id() {return id_;}
inline void setid(int id) {id_ = id;}
inline int depth() {return depth_;}
inline void incrementdepth() {depth_++;}
inline int rank(Vertex* v) {return rank_[v];}
inline int nbrefs(Vertex* v) {return nbrefs_[v];}
inline bool isordered(Vertex* v) {return isordered_[v];}
inline bool isfullref(Vertex* v) {return isfullref_[v];}
inline void switchtoordered(Vertex* v) {isordered_[v] = true;}
inline bool isokforpropagation(Vertex* v) {return isfullref_[v];}
inline int nbordered() {return nbordered_;}
inline int nbfullref() {return nbfullref_;}
inline int nbpartialref() {return nbordered_ - nbfullref_;}
inline void addtochildren(BBNode * n) {children_.push_back(n);};
inline BBNode* getfather() {return father_;}
//
// remove a vertex from the list of potential choice for branching
void removefrompotentialchildren(Vertex* v);
//
// assign the next available rank to the input vertex
void assignnextrank(Vertex* v);
//
// get the current objective value at a node, depending on the ordered vertices
void setnodecost(Problem pb);
//
// propagate the set of ordered vertices by iteratively ordering vertices
// with more than three references
void prepropagation();
//
// propagate the set of ordered vertices by iteratively ordering vertices
// as long as there is either at least one unordered vertex with more than three
// references or exactly one unordered vertex with exactly three references
void postpropagation();
};
/*******************************************************************************
Class BBSolver
- an instance of BBSolver is a solver of the discretization problem using
branch-and-bound
*******************************************************************************/
class BBSolver: public DiscretizationSolver {
private:
// Private characteristics of the BB solver
//
BBNode* rootnode_; // record a root node with no ordered vertex
std::vector<BBNode*> bbnodesqueue_; // list of the nodes created and not treated yet
int primalbound_;
std::map<int, std::vector<BBNode*>> activenodes_; // map containing the nodes that are kept in memory for dominance: they are stored depending on the value of their cost for faster access to only some nodes
int nbactivenodes_; // number of active BB nodes
IloEnv env_; // cplex environment
IloModel relax_; // linear relaxation of the ip model that should be solved
IloCplex cplexrelax_; // cplex solver for the linear relaxation
private:
// Private solution options
//
bool branchonsmallerindex_; // always choose partially-referenced vertex with smallest index to branch first
bool prunesameneighbors_; // prune one node when two branching vertices have the same unordered neighbors
bool checkdominance_; // true if we check at least dominance on the children of each node
bool checkdominancealltree_; // true if dominance is checked on other active nodes
int maxdeltadominance_; // maximum difference in costs when checking dominance
bool improvedualbound_; // true if we solve a small IP to improve dual bounds with clique and cycle cuts
bool userelaxbound_; // true if we solve LP relaxation to get dual bounds
bool exploredepth_;
bool explorebest_;
bool exploredepthbeforebest_;
private:
// Private CPLEX attributes for computation of the dual bound
//
IloEnv envdual_;
IloModel modeldual_;
IloCplex cplexdual_;
BoolVarVertexMap ispartial_;
public:
// Public methods
//
// constructor and destructor
BBSolver(Instance* inst, Problem pb, std::string optionfile, float timelimit = 60);
~BBSolver();
//
// getters and setters
/////////////////////////////////////////////////////////////////////////////
// Output methods
/////////////////////////////////////////////////////////////////////////////
//
// reconstruct the solution with the preallocated vertices
void reconstructsolution();
/////////////////////////////////////////////////////////////////////////////
// Solution methods
/////////////////////////////////////////////////////////////////////////////
//
// execute the branch-and-bound algorithm
int solve();
//
// treat a branch-and-bound node (compute bounds and branch)
void treatnode(BBNode& node);
//
// initialize the IP model for improved dual bound
void initializedualboundIP();
//
// compute a dual bound from the partial order described in the input node
double computedualbound(BBNode& node);
void propagatenode(BBNode& node);
void branch(BBNode& node);
//
// check the dominance of input node with every node in the input vector
bool checkdominancewithlist(BBNode* n1, std::vector<BBNode*>& nodelist);
//
// erase a node from every list where it appears, then delete it
void erasenodefromall(BBNode* node);
//
// erase all the descendants of a node from all list of nodes and delete them
void erasealldescendants(BBNode* node);
//
// 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 dominates(BBNode& n1, BBNode& n2);
};
#endif // __bbsolver__
/********************************************************************************************
Name: DiscretizationSolver
Discrete optimization for graph discretization
Author: J.Omer
Sources: C++
License: GNU General Public License v.2
History:
*********************************************************************************************/
#include "discretizationsolver.hpp"
// Search for all the cycles in the digraph described by the input adjacency
// list and root vertex
bool DiscretizationSolver::enumeratecycles(std::map<Vertex*, std::vector<Vertex*> >& adjlist, Vertex* root,
std::vector<std::vector<Vertex*> >& cycles) {
#ifdef VERBOSE
std::cout << "revorder: enumeration of the cycles" << std::endl;
#endif
// Search for a topological order that will be valid only if the digraph is
// acyclic
std::vector<Vertex*> reverseorder;
std::map<Vertex*, int> rank;
topologicalorder(root, adjlist, reverseorder, rank);
// Search for a set of arc-disjoint cycles
//
std::vector<std::pair<Vertex*, Vertex*> > reverseedges;
if(!this->getreverseedges(adjlist, reverseorder, rank, reverseedges)) {
return false;
}
//
// otherwise, for all reverse arcs search the shortest path from one end point
// to the other following the topological order
int nbcycles = 0;
for(std::pair<Vertex*, Vertex*> a : reverseedges) {
Vertex* v1 = a.first;
Vertex* v2 = a.second;
std::vector<Vertex*> shortestpath;
dijkstra_unitcost(v2, v1, adjlist, rank, shortestpath);
cycles.push_back(shortestpath);
nbcycles += 1;
// if (nbcycles >= 10) return true;
// if (this->modeltype_==WITNESS) return true;
}
return true;
}
//
// search for a topological order compatible with the current integer solution
// of the integer program
// if the graph is not acyclic, the order will be used to identify cycles
void DiscretizationSolver::topologicalorder(Vertex* root,
const std::map<Vertex*, std::vector<Vertex*> >& adjlist,
std::vector<Vertex*>& reverseorder, std::map<Vertex*, int>& rank) {
// Call a depth first search recursively from the root to get a topological
// order that will be valid only if the solution describes an acyclic digraph
//
// call the recursive function from the root
std::map<Vertex*, int> color;
for (Vertex* v : this->inst_->vertices_) color[v] = 0;
while (true) {
depthfirstsearch(root, adjlist, color, reverseorder);
if (reverseorder.size() < this->inst_->nbvertices_) {
for (Vertex* v : this->inst_->vertices_) {
if (color[v] == 0) root = v;
}
}
else break;
}
//
// recover the rank of each vertex
for (Vertex* v : this->inst_->vertices_) rank[v] = -1;
int currentrank = this->inst_->nbvertices_;
for (Vertex* v : reverseorder) {
rank[v] = currentrank--;
}
}
//
// get all the edges that go against the topological order described by the
// inputs rank and reverseorder
// return false if there is none (i.e. if the graph is acyclic)
bool DiscretizationSolver::getreverseedges(std::map<Vertex*, std::vector<Vertex*> >& adjlist,
std::vector<Vertex*>& reverseorder, std::map<Vertex*, int>& rank,
std::vector<std::pair<Vertex*, Vertex*> >& reverseedges){
//
// first, collect all the "reverse" arcs, i.e. the arcs that go against the
// topological order, and delete them from the adjacency lists
for (Vertex* v1 : reverseorder) {
std::vector<Vertex*>::iterator itv2 = adjlist[v1].begin();
while (itv2 != adjlist[v1].end()) {
Vertex* v2 = *itv2;
if (rank[v2] < rank[v1]) {
reverseedges.push_back(std::pair<Vertex*, Vertex*>(v1, v2));
adjlist[v1].erase(itv2);
//
// separate only one cycle in witness decomposition (as described in the article)
// if (this->modeltype_ == WITNESS) return true;
}
else itv2++;
}
}
//
// if there is no reverse arc, stop the digraph is acyclic
if (reverseedges.empty()) return false;
return true;
}
//
// perform a depth-first search to compute a topological order of the digraph
// described by the adjacency list
void DiscretizationSolver::depthfirstsearch(Vertex* u,
const std::map<Vertex*, std::vector<Vertex*> >& adjlist,
std::map<Vertex*, int>& color, std::vector<Vertex*>& inverseorder) {
//
color[u] = 1;
for (Vertex* v: adjlist.at(u)) {
if (color[v] == 0) {
depthfirstsearch(v, adjlist, color, inverseorder);
}
}
color[u] = 2;
inverseorder.push_back(u);
}
//
// apply dijskstra algorithm to find a shortest path from o to d in the unit
// cost digraph described by the input adjacency list
void DiscretizationSolver::dijkstra_unitcost(Vertex* o, Vertex* d, const std::map<Vertex*, std::vector<Vertex*> >& adjlist, std::map<Vertex*, int>& rank, std::vector<Vertex*>& shortestpath) {
//
// perform a breadth-first search from the origin vertex until the destination
// is encountered
std::vector<Vertex*> queue;
queue.push_back(o);
std::map<Vertex*, bool> istreated;
for (Vertex* v : this->inst_->vertices_) istreated[v] = false;
std::map<Vertex*, Vertex*> predecessor;
while (!queue.empty()) {
Vertex* v1 = queue.front();
queue.erase(queue.begin());
for (Vertex* v2 : adjlist.at(v1)) {
if (!istreated[v2] && rank[v2] <= rank[d]) {
predecessor[v2] = v1;
queue.push_back(v2);
istreated[v2] = true;
if (v2 == d) goto cyclefound;
}
}
}
//
// if a path has been found store it as the shortest path
// this is guaranteed by the unit cost and the breadth-first search
cyclefound:
shortestpath.push_back(d);
Vertex* pred = d;
while (pred != o) {
pred = predecessor[pred];
shortestpath.insert(shortestpath.begin(), pred);
};
}
This diff is collapsed.
/********************************************************************************************
Name: DiscretizationSolver
Discrete optimization for graph discretization
Author: J.Omer
Sources: C++
License: GNU General Public License v.2
History:
*********************************************************************************************/
#ifndef __discretizationsolver__
#define __discretizationsolver__
#include "instance.hpp"
#include <map>
#include <algorithm> // std::make_heap, std::pop_heap, std::push_heap, std::sort_heap
#include "ilcplex/ilocplex.h"
#include "ilcplex/cplexx.h"
#include <ilcp/cp.h>
#include <stdio.h>
// Enum types
//
enum Problem {MINPARTIAL};
enum Algorithm{GREEDY, BRANCHANDBOUND, IP, CP, COMPARE};
enum Model {CYCLES, RANKS, CCG, VERTEXRANK, WITNESS, NONE};
static std::map<std::string, Problem> stringToProblem = {{"MINPARTIAL", MINPARTIAL}};
static std::map<Problem, std::string> problemToString = {{MINPARTIAL, "MINPARTIAL"}};
static std::map<Algorithm, std::string> algoToString = {{GREEDY, "GREEDY"},{BRANCHANDBOUND, "BB"},{IP, "IP"},{CP, "CP"}};
static std::map<Model, std::string> modelToString = {{CYCLES, "CYCLES"}, {CCG,"CCG"}, {RANKS,"RANKS"}, {VERTEXRANK,"VERTEXRANK"}, {WITNESS,"WITNESS"}, {NONE, "nomodel"}};
// Types of matrices, maps and arrays of cplex variables
//
typedef IloArray<IloNumVarArray> NumVarMatrix;
typedef IloArray<IloBoolVarArray> BoolVarMatrix;
typedef std::map<Vertex*, std::map<Vertex*, IloBoolVar> > BoolVarVertexMapMatrix;
typedef std::map<Vertex*, std::map<Vertex*, IloBoolVarArray> > BoolVarEdgeMapArray;
typedef std::map<Vertex*, IloBoolVarArray> BoolVarVertexMapArray;
typedef std::map<Vertex*, IloBoolVar> BoolVarVertexMap;
typedef std::map<Vertex*, IloIntVar> IntVarVertexMap;
typedef std::map<Vertex*, std::map<Vertex*, IloNumVar> > NumVarVertexMapMatrix;
typedef std::map<Vertex*, IloNumVarArray> NumVarVertexMapArray;
typedef std::map<Edge*, IloBoolVar> BoolVarMap;
typedef std::map<Vertex*, IloBoolVarArray> BoolVarMapArray;
typedef IloArray<NumVarMatrix> NumVarMatrix3;
// node data structure that will be used to know whether the heuristic callback
// must be called or not
class HeuristicNode: public IloCplex::MIPCallbackI::NodeData{
protected:
bool iscliquefullref_;
int initialcliqueindex_;
public:
HeuristicNode(): iscliquefullref_(false), initialcliqueindex_(-1) {}
inline bool iscliquefullref() {return iscliquefullref_;}
inline int initialcliqueindex() {return initialcliqueindex_;}
inline void fixcliqueindex(int i) {iscliquefullref_ = true; initialcliqueindex_ = i;}
};
class Clique {
protected:
// protected attributes
int nbvertices_;
public:
// public attributes
std::vector<Vertex *> vertices_;
int greedyobjvalue_; // objective value of greedy when starting from this clique
std::vector<Vertex *> initialpartialrefs_; // partially referenced vertices after first propagation of the clique
std::vector<Vertex *> initialfullrefs_; // fully-referenced vertices set during first propagation of the clique
// constructor and destructor
Clique(std::vector<Vertex *> vert);
~Clique();
// access protected attributes
inline int nbvertices() {return nbvertices_;}
};
/*******************************************************************************
Class AverageResults
- store the average results of the instances treated until then
time values are only recorder for the instances solved to optimality
*******************************************************************************/
class AverageResults {
public:
Problem problem_;
Model model_;
Algorithm algo_;
int L_; // min nb of references for discretization orders
int U_; // min nb of references for free ordering
int size_;
int density_;
int nbfeasible_ = 0;
int nboptimal_ = 0;
int nbinstances_ = 0;
float objvaluerelax_ = 0.0; // optimal value of the relaxation of the model
double objvalue_ = 0.0; // optimal value of the integer program
float nbfullref_ = 0; // number of fully-referenced vertices in the discretization order
float relativegap_ = 0.0;
float totaltime_ = 0.0;
float relaxtime_ = 0.0;
float bbnodes_ = 0;
public:
// Public methods
//
// constructor and destructor
AverageResults(Problem pb, Algorithm algo, Model model, int size, int density);
~AverageResults();
};
//
void writeAverageResults(AverageResults& avg, std::string outfile);
/*******************************************************************************
Class AllResults
- store tall the results of the instances treated until then
time values are only recorder for the instances solved to optimality
*******************************************************************************/
class AllResults {
public:
Problem problem_;
Model model_;
Algorithm algo_;
int L_; // min nb of references for discretization orders
int U_; // min nb of references for free ordering
int size_;
int density_;
std::vector<bool> isfeasible_;
std::vector<bool> isoptimal_;
std::vector<std::string> instance_;
std::vector<float> objvaluerelax_; // optimal value of the relaxation of the model
std::vector<double> objvalue_; // optimal value of the integer program
std::vector<float> nbfullref_; // number of fully-referenced vertices in the discretization order
std::vector<int> greedynbfullref_;
std::vector<float> relativegap_;
std::vector<float> totaltime_;
std::vector<float> relaxtime_;
std::vector<float> bbnodes_;
public:
// Public methods
//
// constructor and destructor
AllResults(Problem pb, Algorithm algo, Model model, int size, int density);
~AllResults();
};
//
void writeAllResults(AllResults& results, std::string outfile);
/*******************************************************************************
Class DiscretizationSolver
*******************************************************************************/
class DiscretizationSolver {
protected:
// Private attributes
//
// solution information
float objvaluerelax_ = 0.0; // optimal value of the relaxation of the model
int objvalue_; // optimal value of the integer program
float relativegap_ = 0.0;
bool isfeasible_ = false; // set to true if a feasible solution of the IP has been found
bool isoptimal_ = false; // set to true if an optimal solution of the IP has been found
float totaltime_ = 0.0;
float relaxtime_ = 0.0;
int bbnodes_ = 0; // total number of bb nodes
int treatednodes_ = 0; //number of bb nodes treated up to now in branch-and-bound
//
// information about the solution of the greedy heuristic
int greedynbfullref_ = 0;
//
// identification of cuts
std::vector<Clique*> cliqueswithpartialref_;
std::map<Clique*, int> nbpartialinclique_;
//
// option of the solver
bool decomposecomponents_; // if true, regularly identify connected components of unordered vertices and decompose the problem accordingly
int initialcyclesize_; // size of the cycle cuts initially included in CCG and WITNESS (default is 3)
int cliquecutsmaxsize_; // maximum size of cliques in clique cuts (default is 6)
public:
//
// cplex variables
//
// variables that represent the order relations between the vertices
// - In CCG, isbefore[i,j] = 1 if vertex i is before j in the order
// - in WITNESS, isbefore[i,j] = 1 if and only if i is a witness of j (see Bodur and MacNeil 2019 for description)
BoolVarVertexMapMatrix isbefore_;
// isfullref[v] =1 if vertex v is fully-referenced and 0 otherwise
BoolVarVertexMap isfullref_;
// - hasrank[v,k] = 1 if vertex v has rank k in the discretization order
BoolVarVertexMapArray hasrank_;
// - rank[v] = integer variable representing the rank of v in the order
IntVarVertexMap rank_;
//
</