Commits (24)
To build the source code, you'll need:
WCSPLift is a program that relates to the Constraint Composite Graph for the WCSP. This version is
applicable on the non-Boolean WCSP.
- A C++ compiler that supports C++ 11 (the code is known to build with gcc 4.9)
The source repository has two important branches: The master branch, which works only for the
Boolean WCSP, and the high-domain-clique branch, which also works for the non-Boolean WCSP. While
the latter is more generally applicable, in general, the former can be deemed more efficient on the
Boolean WCSP. Make sure you have downloaded from the desired branch.
To build the source code, you'll need set up the following dependencies:
- A C++ compiler that supports C++ 11 (the code is known to build with gcc 4.9+)
- A POSIX-compliant system
- The boost library
- The Gurobi optimizer
- cmake (build dependency only)
To build the binary executable, you can follow the standard cmake project building procedure:
cd /path/to/the/root/of/the/source/tree
cmake .
cmake-gui . # (or `ccmake .`) adjust build options
Keep in mind that if you are not developing WCSPLift, you probably want to change the
"CMAKE_BUILD_TYPE" build option to "Release" to build a more optimized executable.
To build the documentation (optional), please read doc/README.
The executable will be put under the "bin" directory named "wcsp". Run the executable with the
"--help" option to view available options.
`--help` option to view available options.
The default input file format is DIMACS: http://graphmod.ics.uci.edu/group/WCSP_file_format
The default input file format is DIMACS <http://graphmod.ics.uci.edu/group/WCSP_file_format>. See
format.txt for details.
As a shortcut, to solve a WCSP instance using message passing on the Constraint Composite Graph,
simply use the following command:
wcsp -m m [input-file]
To build the documentation, please read doc/README.
- Hong Xu, T. K. Satish Kumar, and Sven Koenig. The Nemhauser-Trotter reduction and lifted message passing for the weighted CSP. In Proceedings of the 14th International Conference on Integration of Artificial Intelligence and Operations Research Techniques in Constraint Programming (CPAIOR), 387–402. 2017. doi:10.1007/978-3-319-59776-8_31.
- Hong Xu, Sven Koenig, and T. K. Satish Kumar. A constraint composite graph-based ILP encoding of the Boolean weighted CSP. In Proceedings of the 23rd International Conference on Principles and Practice of Constraint Programming (CP). 2017.
Requesting Help Regarding the Build Procedure and Basic Usage
To request help regarding the build procedure and basic usage, please open a new issue at the issue
tracker <https://gitlab.com/xuphys/wcsp-solver/issues> and I will respond you there. Please avoid
using other means such as email. All such requests shall be dismissed if they are not initiated on
the issue tracker. The rationales are as follows:
- The answers to these questions are often valuable to anyone else who may be in a similar
situation. An issue tracker facilitates the reuse of these answers.
- An issue tracker makes the management much easier.
When opening a new issue, please make sure that you have provided sufficient details, such as the
details of the error message and the steps that you made to produce them. Please also label the type
of the issue in the "Labels" field. Please try to avoid screenshots if possible, since texts are
much more easily searchable. For example, copy and paste command line input and output instead of
taking a screenshot of the terminal window. Please also keep mind to not post private personal
information, as I may not be able to delete them and this issue tracker is publicly accessible.
What Can I ask?
For questions, please post them only if they are on the build procedure or basic usage of WCSPLift
on the issue tracker. Unfortunately, I am always very busy and too many questions are likely to
degrade the quality of my answers. For this reason, I will dismiss quesitons on the issue tracker if
they fall into any of the following categories:
- Questions that have already been clearly answered in README and past issues (Before openning a new
issue, please carefully read this README once, search existing issues, and make sure that your
question has not been answered.)
- Questions on basic command line usage (Try search the Internet.)
- Questions on how to install and configure dependencies (Try search the Internet.)
- Questions on related research questions (These questions should be sent to relevant parties, which
may include me, commonly via email.)
An exception to this list is clarification questions of text in the README. For example, there are
some details on how to deal with Gurobi in the FAQ section. While questions on installing and
configuring Gurobi falls into the category "Questions on how to install and configure dependencies,"
clarification questions on Gurobi-related text in README shall be accepted.
How do I Request In-Person or Phone Help?
I will always only respond to questions initiated on the issue tracker. When you see a sufficiently
high probability that the issue cannot be resolved on the issue tracker, please make an in-person or
phone help request **on the issue tracker**. I will respond to your request there and then we will
switch to emails for meeting details, because, in this way, future in-person and phone help
requester can gain some understanding on the situations in which such requests shall be approved.
Any in-person or phone help request without any associated issue numbers shall be dismissed.
Q: How to install the dependencies?
A: You can install the dependencies using your favorite approaches. If you have never installed
dependencies, try to use a software package manager for your system.
Q: F88k Gurobi! The compiler/linker always complains something about it!
I agree with you :P. Gurobi unfortunately does not provide a reliable way for the cmake build script
to automatically detect it during the build process. Therefore, you'll need to manually set it up.
If you are not sure about how to do it, here is a script that I use myself and should work on most
POSIX-compatible shells:
export GUROBI_HOME="$HOME/.local/opt/gurobi/linux64" # replace this with your own Gurobi installation directory
export PATH="${PATH}:${GUROBI_HOME}/bin"
export LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${GUROBI_HOME}/lib" # Remove this line if on MacOS
export INCLUDE="${INCLUDE}:${GUROBI_HOME}/include"
You can save this file (for example, named `setup.sh`) and source it when you plan to use WCSPLift
in a shell session.
Many "experts" on the Internet often proudly teach you a trick to "just" put these kinds of lines to
your shell initialization scripts, such as `.profile`, `.bashrc`, or `.bashrc`, for the sake of
convenience. I highly recommend you not to do so, since this will effectively enable Gurobi's
included libraries system-widely or user-widely, and this can potentially cause you trouble if there
is a conflict with your system libraries and the ones with the same names shipped by Gurobi. (Gurobi
includes a lot of libraries that are of versions that are different from the ones installed in your
Q: I'm on MacOS and my compiler complains about a missing file named "cblas.h." How do I fix this?
Consider the solution that makes use of openblas from Homebrew (modified from
https://stackoverflow.com/a/35638559/1150462 ):
brew uninstall openblas; brew install --fresh -vd openblas
cmake -DCMAKE_CXX_FLAGS="-I$(brew --prefix openblas)/include" .
Q: The executable I built seems slow even on small problem instances. Why?
While there is certainly a possibility that the reason lies in the nature of the problem instances,
often this is simply caused by the fact that you did not change the CMAKE_BUILD_TYPE build option to
Copyright (c) 2016-2017 Hong Xu
Copyright (c) 2016-2018 Hong Xu
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
First line: <Name> <Number of variables (N)> <Maximum domain size>
<Number of constraints> <Global upper bound of weights (greater than the
maximum weight of all constraints)>
Second line: <Domain size of variable 0> ... <Domain size of variable N-1>
Starting from the 3rd line, constraints are listed one after another.
For each constraint,
First line: <Arity of constraint> <index of 1st variable in the
constraint> ... <index of last variable in the constraint> <Default
weight> <Number of tuples with weights different from the default weight>
Starting from the second line of each constraint, each weight is listed
per line.
<value assigned to the first variable> <value assigned to the second
variable> ... <value assigned to the last variable> <weight of this tuple>
For example, the WCSP on the 5th page of the slides
http://files.hong.me/papers/xu2017-slides.pdf (All lines starting with
# are my comments. They won't appear in the real files.)
# Problem description
Whatever 3 2 6 2
# Domain sizes
2 2 2
#First unary constraint
1 0 0 2
0 0.7
1 0.2
# Second unary constraint
1 1 0 2
0 0.3
1 0.8
# Third unary constraint
1 2 0 2
0 0.1
1 1.0
# First binary constraint
2 0 1 0 4
0 0 0.5
0 1 0.6
1 0 0.7
1 1 0.3
# Second binary constraint
2 0 2 0 4
0 0 0.6
0 1 1.3
1 0 1.0
1 1 1.1
# Third binary constraint
2 1 2 0 4
0 0 0.4
0 1 0.9
1 0 0.7
1 1 0.8
......@@ -31,8 +31,10 @@ endif()
# For now, we assume Gurobi is always available
# You may need to change gurobi_g++5.2 to gurobi_g++4.2 if you are using an older version of gcc.
set(GUROBI_LIBRARIES "gurobi_g++5.2" "gurobi75")
set(GUROBI_VERSION "80" CACHE STRING "Gurobi version.")
"You may need to adapt the value of the C++ library according to your system. Please read into your Gurobi library directory (usually ${GUROBI_HOME}/lib) for available files.")
......@@ -263,6 +263,40 @@ public:
return p[PolynomialKey()];
/** \brief Build the cliques that represent every single variable.
* \param[in] variables The mapping from non-Boolean variables to Boolean variables.
inline void addCliques(const std::vector<std::vector<variable_id_t> >& variables)
using namespace boost;
for (const auto& nbv : variables)
if (nbv.size() == 1)
// all vertices that represent the variable
std::vector<vertex_t> all_vertices;
for (const auto& id : nbv)
// id_to_v does not contain id, which means that nbv may not appear in the WCSP at
// all.
if (id_to_v.count(id) == 0)
// Connect every two vertices in all_vertices
for (auto it = all_vertices.begin(); it != all_vertices.end(); ++ it)
for (auto it1 = std::next(it); it1 != all_vertices.end(); ++ it1)
add_edge(*it, *it1, g);
/** \brief Simplify the graph by removing all zero weight vertices and all edges incident to
* them.
This diff is collapsed.
......@@ -40,8 +40,10 @@ static void print_results(const T1& instance, const T2& assignments)
std::cout << "Timeout solution" << std::endl;
std::cout << "Best assignments:" << std::endl;
std::cout << "ID\tassignment" << std::endl;
for (auto a : assignments)
std::cout << a.first << '\t' << a.second << std::endl;
for (const auto& v : assignments)
std::cout << v.first << '\t' << v.second << std::endl;
std::cout << "Optimal value: " << opt_value << std::endl;
......@@ -77,7 +79,9 @@ int main(int argc, const char** argv)
("ccg-only,g", "print the CCG only without solving the MWVC problem on it")
("no-kernelization,k", "don't kernelize")
("kernelization-only,K", "exit after kernelization")
#if 0
("message-passing,M", "do message passing on the original problem directly")
("linear-programming,L", "use linear programming to solve the original problem directly")
("mwvc-solver,m", po::value<>(&mwvc_solver)->default_value('l')->notifier(
[](char x){
......@@ -163,10 +167,12 @@ int main(int argc, const char** argv)
WCSPInstance<> instance(in, fformat);
if (time_limit != std::numeric_limits<double>::max()) // time limit is set
#if 0
if (vm.count("message-passing"))
std::map<WCSPInstance<>::variable_id_t, bool> assignments =
......@@ -174,6 +180,7 @@ int main(int argc, const char** argv)
print_results(instance, assignments);
return 0;
if (vm.count("linear-programming"))
......@@ -190,6 +197,7 @@ int main(int argc, const char** argv)
// s is the remnant weight
ConstraintCompositeGraph<>::weight_t s = ccg.addPolynomial(p);
// The reason that we use a map instead of a set to represent the vertex cover is that after
// each step, we can see what variables have been assigned and what have not.
......@@ -280,7 +288,33 @@ int main(int argc, const char** argv)
std::cout << "=================================================" << std::endl;
print_results(instance, assignments);
std::map<WCSPInstance<>::variable_id_t, WCSPInstance<>::non_boolean_value_t> solution;
for (WCSPInstance<>::variable_id_t v = 0; v < instance.getNonBooleanVariables().size(); ++ v)
const auto& bvs = instance.getNonBooleanVariables()[v];
// no assignment specified for bvs[0], which mean it can be any value (e.g., not in the
// WCSP). No need to modify solution.
if (assignments.count(bvs[0]) == 0)
if (bvs.size() == 1)
solution[v] = assignments.at(bvs[0]);
auto false_v = std::find_if_not(
bvs.begin(), bvs.end(),
[&](ConstraintCompositeGraph<>::variable_id_t id){return assignments.at(id);});
if (false_v == bvs.end()) // No variable is false
solution[v] = 0;
solution[v] = std::distance(bvs.begin(), false_v) + 1;
print_results(instance, solution);
return 0;
unknown 3 3 3 9999
2 3 3
2 0 1 0 6
0 0 1.1
1 0 1.0
0 1 0.2
1 1 0.5
0 2 0.1
1 2 0.4
2 1 2 0 9
0 0 0.3
1 0 1.0
2 0 0.1
0 1 0.2
1 1 0.5
2 1 0.6
0 2 0.4
1 2 0.5
2 2 0.5
3 0 1 2 0 6
0 0 1 1.1
1 0 1 1.0
0 1 1 0.2
1 1 0 0.5
0 2 0 0.1
1 2 0 0.4
Best assignments:
ID assignment
0 0
1 2
2 0
Optimal value: 0.3
unknown 3 2 1 9999
2 2 3
3 0 1 2 0 12
0 0 0 0.5
0 0 1 0.3
0 0 2 0.2
0 1 0 0.3
0 1 1 0.2
0 1 2 0.2
1 0 0 0.5
1 0 1 1.0
1 0 2 1.0
1 1 0 0.2
1 1 1 0.5
1 1 2 0.1
2 2 3
3 0 1 2
0.5 0.3 0.2
0.3 0.2 0.2
0.5 1.0 1.1
0.2 0.5 0.1