This is the fourth part of the SAT series. For your convenience you can find other parts in the table of contents in Part 1 — Boolean logic
Let’s use C++ templates to solve 3CNF SAT in compile-time.
Table of Contents
The solution
First, let’s start with the code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 |
#include <iostream> using namespace std; // ============================ class NullType {}; // ============================ template <class T, class U> struct Typelist { typedef T Head; typedef U Tail; }; // ============================ template <class TList, unsigned int index> struct TypeAt; template <class Head, class Tail> struct TypeAt<Typelist<Head, Tail>, 0> { typedef Head Result; }; template <class Head, class Tail, unsigned int i> struct TypeAt<Typelist<Head, Tail>, i> { typedef typename TypeAt<Tail, i - 1>::Result Result; }; // ============================ template <class TList, class TAggregated> struct ReversedTypelist; template<class TAggregated> struct ReversedTypelist<NullType, TAggregated>{ typedef TAggregated Result; }; template <class Head, class Tail, class TAggregated> struct ReversedTypelist<Typelist<Head, Tail>, TAggregated> { typedef typename ReversedTypelist<Tail, Typelist<Head, TAggregated> >::Result Result; }; // ============================ template<int v> struct Literal { enum { value = v }; }; struct LiteralTrue { enum { value = 1 }; }; struct LiteralFalse { enum { value = 0 }; }; // ============================ template<typename T> struct Id { typedef T Result; }; template<typename T> struct Negated {}; template<> struct Negated<LiteralTrue> { typedef LiteralFalse Result; }; template<> struct Negated<LiteralFalse> { typedef LiteralTrue Result; }; // ============================ template<int i> struct Variable {}; template<> struct Variable<0> { typedef LiteralFalse value1; typedef LiteralFalse value2; }; template<> struct Variable<1> { typedef LiteralTrue value1; typedef LiteralTrue value2; }; template<> struct Variable<2> { typedef LiteralFalse value1; typedef LiteralTrue value2; }; // ============================ template<typename Constraint, typename BoundVariables> struct Conjunction { }; template<typename BoundVariables> struct Conjunction<LiteralFalse, BoundVariables> { typedef NullType Result; }; template<typename BoundVariables> struct Conjunction<LiteralTrue, BoundVariables> { typedef BoundVariables Result; }; template<typename BoundVariables1, typename BoundVariables2> struct Disjunction { typedef BoundVariables1 Result; }; template<> struct Disjunction<NullType, NullType> { typedef NullType Result; }; template<typename BoundVariables1> struct Disjunction<BoundVariables1, NullType> { typedef BoundVariables1 Result; }; template<typename BoundVariables2> struct Disjunction<NullType, BoundVariables2> { typedef BoundVariables2 Result; }; // ============================ // ============================ // Constraints with actual calculation // This is where we check if SAT clauses (Variables) really satisfy a particular constraint template<typename AOrNegated, typename BOrNegated, typename COrNegated> struct Constraint {}; template<typename A, typename B, typename C> struct ThreeClause { }; template<> struct ThreeClause<LiteralFalse, LiteralFalse, LiteralFalse> { typedef LiteralFalse value; }; template<> struct ThreeClause<LiteralFalse, LiteralFalse, LiteralTrue> { typedef LiteralTrue value; }; template<> struct ThreeClause<LiteralFalse, LiteralTrue, LiteralFalse> { typedef LiteralTrue value; }; template<> struct ThreeClause<LiteralFalse, LiteralTrue, LiteralTrue> { typedef LiteralTrue value; }; template<> struct ThreeClause<LiteralTrue, LiteralFalse, LiteralFalse> { typedef LiteralTrue value; }; template<> struct ThreeClause<LiteralTrue, LiteralFalse, LiteralTrue> { typedef LiteralTrue value; }; template<> struct ThreeClause<LiteralTrue, LiteralTrue, LiteralFalse> { typedef LiteralTrue value; }; template<> struct ThreeClause<LiteralTrue, LiteralTrue, LiteralTrue> { typedef LiteralTrue value; }; // ============================ // ============================ // ============================ // BoundConstraints // Extracts referenced Variables and calculates literal representing the result of logical operation template<typename ConstraintType, typename BoundVariables> struct BoundConstraint {}; template<int A, int B, int C, typename BoundVariables> struct BoundConstraint<Constraint<Id<Literal<A> >, Id<Literal<B> >, Id<Literal<C> > >, BoundVariables> { typedef typename ThreeClause< typename Id<typename TypeAt<BoundVariables, A>::Result>::Result, typename Id<typename TypeAt<BoundVariables, B>::Result>::Result, typename Id<typename TypeAt<BoundVariables, C>::Result>::Result >::value value; }; template<int A, int B, int C, typename BoundVariables> struct BoundConstraint<Constraint<Id<Literal<A> >, Id<Literal<B> >, Negated<Literal<C> > >, BoundVariables> { typedef typename ThreeClause< typename Id<typename TypeAt<BoundVariables, A>::Result>::Result, typename Id<typename TypeAt<BoundVariables, B>::Result>::Result, typename Negated<typename TypeAt<BoundVariables, C>::Result>::Result >::value value; }; template<int A, int B, int C, typename BoundVariables> struct BoundConstraint<Constraint<Id<Literal<A> >, Negated<Literal<B> >, Id<Literal<C> > >, BoundVariables> { typedef typename ThreeClause< typename Id<typename TypeAt<BoundVariables, A>::Result>::Result, typename Negated<typename TypeAt<BoundVariables, B>::Result>::Result, typename Id<typename TypeAt<BoundVariables, C>::Result>::Result >::value value; }; template<int A, int B, int C, typename BoundVariables> struct BoundConstraint<Constraint<Id<Literal<A> >, Negated<Literal<B> >, Negated<Literal<C> > >, BoundVariables> { typedef typename ThreeClause< typename Id<typename TypeAt<BoundVariables, A>::Result>::Result, typename Negated<typename TypeAt<BoundVariables, B>::Result>::Result, typename Negated<typename TypeAt<BoundVariables, C>::Result>::Result >::value value; }; template<int A, int B, int C, typename BoundVariables> struct BoundConstraint<Constraint<Negated<Literal<A> >, Id<Literal<B> >, Id<Literal<C> > >, BoundVariables> { typedef typename ThreeClause< typename Negated<typename TypeAt<BoundVariables, A>::Result>::Result, typename Id<typename TypeAt<BoundVariables, B>::Result>::Result, typename Id<typename TypeAt<BoundVariables, C>::Result>::Result >::value value; }; template<int A, int B, int C, typename BoundVariables> struct BoundConstraint<Constraint<Negated<Literal<A> >, Id<Literal<B> >, Negated<Literal<C> > >, BoundVariables> { typedef typename ThreeClause< typename Negated<typename TypeAt<BoundVariables, A>::Result>::Result, typename Id<typename TypeAt<BoundVariables, B>::Result>::Result, typename Negated<typename TypeAt<BoundVariables, C>::Result>::Result >::value value; }; template<int A, int B, int C, typename BoundVariables> struct BoundConstraint<Constraint<Negated<Literal<A> >, Negated<Literal<B> >, Id<Literal<C> > >, BoundVariables> { typedef typename ThreeClause< typename Negated<typename TypeAt<BoundVariables, A>::Result>::Result, typename Negated<typename TypeAt<BoundVariables, B>::Result>::Result, typename Id<typename TypeAt<BoundVariables, C>::Result>::Result >::value value; }; template<int A, int B, int C, typename BoundVariables> struct BoundConstraint<Constraint<Negated<Literal<A> >, Negated<Literal<B> >, Negated<Literal<C> > >, BoundVariables> { typedef typename ThreeClause< typename Negated<typename TypeAt<BoundVariables, A>::Result>::Result, typename Negated<typename TypeAt<BoundVariables, B>::Result>::Result, typename Negated<typename TypeAt<BoundVariables, C>::Result>::Result >::value value; }; // ============================ // ============================ // Model template<typename Variables, typename BoundVariables, typename Constraints, typename BoundConstraints> struct Model {}; // Model with no BoundConstraints just returns the bound variables template<typename BoundVariables> struct Model<NullType, BoundVariables, NullType, NullType>{ typedef BoundVariables Result; }; // Recursively calculates AND between current BoundConstraint and the rest of BoundConstraints // Effectively removes one element from BoundConstraints, and returns Conjunction of the removed BoundConstraint and the rest template<typename BoundVariables, typename Head, typename Tail> struct Model<NullType, BoundVariables, NullType, Typelist<Head, Tail> > { typedef typename Conjunction< typename Head::value, typename Model<NullType, BoundVariables, NullType, Tail>::Result >::Result Result; }; // Recursively bounds Constraint with BoundVariables // Effectively removes one element from Constraints, and adds one element to BoundConstraints template<typename BoundVariables, typename Head, typename Tail, typename BoundConstraints> struct Model<NullType, BoundVariables, Typelist<Head, Tail>, BoundConstraints> { typedef typename Model<NullType, BoundVariables, Tail, Typelist<BoundConstraint<Head, BoundVariables>, BoundConstraints> >::Result Result; }; // Takes first Variable, branches into two trees for the first value of the variable and the second value // Effectively removes one element from Variables, and adds one element to BoundVariables template<typename Head, typename Tail, typename BoundVariables, typename Constraints, typename BoundConstraints> struct Model<Typelist<Head, Tail>, BoundVariables, Constraints, BoundConstraints> { typedef typename Disjunction< typename Model<Tail, Typelist<typename Head::value1, BoundVariables>, Constraints, BoundConstraints>::Result, typename Model<Tail, Typelist<typename Head::value2, BoundVariables>, Constraints, BoundConstraints>::Result >::Result Result; }; template<typename Variables, typename Constraints> struct FullModel { typedef typename Model<typename ReversedTypelist<Variables, NullType>::Result, NullType, Constraints, NullType>::Result Result; }; // ============================ // ============================ // Print helpers template<typename T> struct Print {}; template<typename Head, typename Tail> struct Print<Typelist<Head, Tail> >{ void print(){ std::cout<<Head::value<<std::endl; Print<Tail>().print(); } }; template<> struct Print<NullType>{ void print(){ std::cout<<"NullType"<<std::endl; } }; // ============================ int main() { // XOR Print<typename FullModel< Typelist<Variable<1>, Typelist<Variable<0>, Typelist<Variable<2>, NullType> > >, Typelist<Constraint<Negated<Literal<0> >, Id<Literal<1> >, Id<Literal<2> > >, Typelist<Constraint<Id<Literal<0> >, Negated<Literal<1> >, Id<Literal<2> > >, Typelist<Constraint<Id<Literal<0> >, Id<Literal<1> >, Negated<Literal<2> > >, Typelist<Constraint<Negated<Literal<0> >, Negated<Literal<1> >, Negated<Literal<2> > >, NullType> > > > >::Result>().print(); } |
Problem definition
Let’s start with the main
function which shows how to calculate exclusive or (XOR) using this approach:
1 2 3 4 5 6 7 8 |
Print<typename FullModel< Typelist<Variable<1>, Typelist<Variable<0>, Typelist<Variable<2>, NullType> > >, Typelist<Constraint<Negated<Literal<0> >, Id<Literal<1> >, Id<Literal<2> > >, Typelist<Constraint<Id<Literal<0> >, Negated<Literal<1> >, Id<Literal<2> > >, Typelist<Constraint<Id<Literal<0> >, Id<Literal<1> >, Negated<Literal<2> > >, Typelist<Constraint<Negated<Literal<0> >, Negated<Literal<1> >, Negated<Literal<2> > >, NullType> > > > >::Result>().print(); |
We will create an instance of FullModel
template that will encapsulate our parameters.
As a first parameter, we are going to pass a list of variables. Each variable can be either fixed (Variable<0>
is false, Variable<1>
is true) or free (Variable<2>
). Fixed variables represent clauses for which we know the value. Free variables need to be solved by the solver. We’ll later refer to this list as list of variables and we’ll get its elements by the 0-based indices.
The second parameter is a list of constraints. Each constraint represents a disjunction of three variables. Each variable is either negated or left intact. Let’s take this line as an example:
1 |
Constraint<Negated<Literal<0> >, Id<Literal<1> >, Id<Literal<2> > |
This creates a constraint in which we take variables with numbers 0, 1, and 2. The first variable (with index 0) is negated, other ones are left intact. This constraint represents the following formula:
Helpers
We need some helper structures to represent concepts.
1 |
class NullType {}; |
This represents a null type. It’s basically a sentinel to indicate end of list or missing values.
Next, we need a definition of linked list. We build it the typical way, with head and tail:
1 2 3 4 5 6 |
template <class T, class U> struct Typelist { typedef T Head; typedef U Tail; }; |
We need some helpers to operate on this list. We start with indexing:
1 2 3 4 5 6 7 8 9 10 11 |
template <class TList, unsigned int index> struct TypeAt; template <class Head, class Tail> struct TypeAt<Typelist<Head, Tail>, 0> { typedef Head Result; }; template <class Head, class Tail, unsigned int i> struct TypeAt<Typelist<Head, Tail>, i> { typedef typename TypeAt<Tail, i - 1>::Result Result; }; |
We also need a way to reverse the list:
1 2 3 4 5 6 7 8 9 10 |
template <class TList, class TAggregated> struct ReversedTypelist; template<class TAggregated> struct ReversedTypelist<NullType, TAggregated>{ typedef TAggregated Result; }; template <class Head, class Tail, class TAggregated> struct ReversedTypelist<Typelist<Head, Tail>, TAggregated> { typedef typename ReversedTypelist<Tail, Typelist<Head, TAggregated> >::Result Result; }; |
We assume that each list will end with a null type and will not have any null type in the middle.
Now, we need a literal that represents a number used for indexing inside the list:
1 2 3 4 5 |
template<int v> struct Literal { enum { value = v }; }; |
In a similar way, we need two literals to represent true and false. They are used to represent the actual variable values:
1 2 3 4 5 6 7 8 9 10 11 |
struct LiteralTrue { enum { value = 1 }; }; struct LiteralFalse { enum { value = 0 }; }; |
Finally, we need to have a way to negate the variable value or leave it intact.
1 2 3 4 |
template<typename T> struct Id { typedef T Result; }; template<typename T> struct Negated {}; template<> struct Negated<LiteralTrue> { typedef LiteralFalse Result; }; template<> struct Negated<LiteralFalse> { typedef LiteralTrue Result; }; |
Representing variables
Let’s now define actual variables used in the model. They represent logical clauses that can be either true or false.
Let’s now sketch the general idea for how we’re going to solve the model. We’ll try to substitute each clause with the allowed values. For fixed variables, only one value is allowed (either 0 or 1). For free variables, we need to consider both values (0 and 1). Therefore, each variable will have two fields to store the available values. For fixed variables, both fields will have the same value. For free variables, the fields will have two different values.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
template<int i> struct Variable {}; template<> struct Variable<0> { typedef LiteralFalse value1; typedef LiteralFalse value2; }; template<> struct Variable<1> { typedef LiteralTrue value1; typedef LiteralTrue value2; }; template<> struct Variable<2> { typedef LiteralFalse value1; typedef LiteralTrue value2; }; |
Constraints
When solving the model, we’ll effectively examine each possible valuation. Therefore, constraints need to be resolved after valuating variables. Therefore, we’ll have a two-stage process: first, we’ll define constraints as which variables they should incorporate. In the second stage, the constraints will be bound with valuated variables.
To do that, we will need to calculate the CNF. Therefore, we’ll need to calculate the conjunction of all constraints, and disjunction of possible valuations. We define these as follows:
1 2 3 4 5 6 7 8 |
template<typename Constraint, typename BoundVariables> struct Conjunction { }; template<typename BoundVariables> struct Conjunction<LiteralFalse, BoundVariables> { typedef NullType Result; }; template<typename BoundVariables> struct Conjunction<LiteralTrue, BoundVariables> { typedef BoundVariables Result; }; template<typename BoundVariables1, typename BoundVariables2> struct Disjunction { typedef BoundVariables1 Result; }; template<> struct Disjunction<NullType, NullType> { typedef NullType Result; }; template<typename BoundVariables1> struct Disjunction<BoundVariables1, NullType> { typedef BoundVariables1 Result; }; template<typename BoundVariables2> struct Disjunction<NullType, BoundVariables2> { typedef BoundVariables2 Result; }; |
Conceptually, we’ll use them as following:
1 |
Disjunction(first valuation, Disjunction(second valuation, Disjunction(third valuation, ...))) |
For each valuation, we’ll calculate the following:
1 |
Conjunction(first constraint, Conjunction(second constraint, Conjunction(third constraint, ...))) |
This will effectively create a tree of valuations (thanks to disjunctions), and for each specific valuation we’ll verify if all 3CNF clauses are met (thanks to conjunction).
Notice that both conjunction and disjunction return a set of bound variables. We use that to retrieve the particular result. If a given valuation doesn’t meet the constraints, then the null type is returned.
Now, we need to actually be able to check if a particular 3CNF is solved. We do it like this:
1 2 3 4 5 6 7 8 9 10 11 |
template<typename AOrNegated, typename BOrNegated, typename COrNegated> struct Constraint {}; template<typename A, typename B, typename C> struct ThreeClause { }; template<> struct ThreeClause<LiteralFalse, LiteralFalse, LiteralFalse> { typedef LiteralFalse value; }; template<> struct ThreeClause<LiteralFalse, LiteralFalse, LiteralTrue> { typedef LiteralTrue value; }; template<> struct ThreeClause<LiteralFalse, LiteralTrue, LiteralFalse> { typedef LiteralTrue value; }; template<> struct ThreeClause<LiteralFalse, LiteralTrue, LiteralTrue> { typedef LiteralTrue value; }; template<> struct ThreeClause<LiteralTrue, LiteralFalse, LiteralFalse> { typedef LiteralTrue value; }; template<> struct ThreeClause<LiteralTrue, LiteralFalse, LiteralTrue> { typedef LiteralTrue value; }; template<> struct ThreeClause<LiteralTrue, LiteralTrue, LiteralFalse> { typedef LiteralTrue value; }; template<> struct ThreeClause<LiteralTrue, LiteralTrue, LiteralTrue> { typedef LiteralTrue value; }; |
This is quite straightforward. We take the 3CNF and check if any variable is true.
Now, we need to bound the constraints with the variables. To do that, we take the variable by its index, negate it if needed, and use in the 3CNF:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
template<typename ConstraintType, typename BoundVariables> struct BoundConstraint {}; template<int A, int B, int C, typename BoundVariables> struct BoundConstraint<Constraint<Id<Literal<A> >, Id<Literal<B> >, Id<Literal<C> > >, BoundVariables> { typedef typename ThreeClause< typename Id<typename TypeAt<BoundVariables, A>::Result>::Result, typename Id<typename TypeAt<BoundVariables, B>::Result>::Result, typename Id<typename TypeAt<BoundVariables, C>::Result>::Result >::value value; }; template<int A, int B, int C, typename BoundVariables> struct BoundConstraint<Constraint<Id<Literal<A> >, Id<Literal<B> >, Negated<Literal<C> > >, BoundVariables> { typedef typename ThreeClause< typename Id<typename TypeAt<BoundVariables, A>::Result>::Result, typename Id<typename TypeAt<BoundVariables, B>::Result>::Result, typename Negated<typename TypeAt<BoundVariables, C>::Result>::Result >::value value; }; template<int A, int B, int C, typename BoundVariables> struct BoundConstraint<Constraint<Id<Literal<A> >, Negated<Literal<B> >, Id<Literal<C> > >, BoundVariables> { typedef typename ThreeClause< typename Id<typename TypeAt<BoundVariables, A>::Result>::Result, typename Negated<typename TypeAt<BoundVariables, B>::Result>::Result, typename Id<typename TypeAt<BoundVariables, C>::Result>::Result >::value value; }; template<int A, int B, int C, typename BoundVariables> struct BoundConstraint<Constraint<Id<Literal<A> >, Negated<Literal<B> >, Negated<Literal<C> > >, BoundVariables> { typedef typename ThreeClause< typename Id<typename TypeAt<BoundVariables, A>::Result>::Result, typename Negated<typename TypeAt<BoundVariables, B>::Result>::Result, typename Negated<typename TypeAt<BoundVariables, C>::Result>::Result >::value value; }; template<int A, int B, int C, typename BoundVariables> struct BoundConstraint<Constraint<Negated<Literal<A> >, Id<Literal<B> >, Id<Literal<C> > >, BoundVariables> { typedef typename ThreeClause< typename Negated<typename TypeAt<BoundVariables, A>::Result>::Result, typename Id<typename TypeAt<BoundVariables, B>::Result>::Result, typename Id<typename TypeAt<BoundVariables, C>::Result>::Result >::value value; }; template<int A, int B, int C, typename BoundVariables> struct BoundConstraint<Constraint<Negated<Literal<A> >, Id<Literal<B> >, Negated<Literal<C> > >, BoundVariables> { typedef typename ThreeClause< typename Negated<typename TypeAt<BoundVariables, A>::Result>::Result, typename Id<typename TypeAt<BoundVariables, B>::Result>::Result, typename Negated<typename TypeAt<BoundVariables, C>::Result>::Result >::value value; }; template<int A, int B, int C, typename BoundVariables> struct BoundConstraint<Constraint<Negated<Literal<A> >, Negated<Literal<B> >, Id<Literal<C> > >, BoundVariables> { typedef typename ThreeClause< typename Negated<typename TypeAt<BoundVariables, A>::Result>::Result, typename Negated<typename TypeAt<BoundVariables, B>::Result>::Result, typename Id<typename TypeAt<BoundVariables, C>::Result>::Result >::value value; }; template<int A, int B, int C, typename BoundVariables> struct BoundConstraint<Constraint<Negated<Literal<A> >, Negated<Literal<B> >, Negated<Literal<C> > >, BoundVariables> { typedef typename ThreeClause< typename Negated<typename TypeAt<BoundVariables, A>::Result>::Result, typename Negated<typename TypeAt<BoundVariables, B>::Result>::Result, typename Negated<typename TypeAt<BoundVariables, C>::Result>::Result >::value value; }; |
I imagine this part can be simplified somehow (with even more templates and higher-kinded templates).
Model
We now have everything to define the model. A model is a structure with 4 elements: list of variables, list of bound variables (variables with valuation), list of constraints, and a list of bound constraints:
1 |
template<typename Variables, typename BoundVariables, typename Constraints, typename BoundConstraints> struct Model {}; |
We don’t want the user to provide all the values, as we just need the variables and constraints. Therefore, we have the following wrapper:
1 2 3 4 |
template<typename Variables, typename Constraints> struct FullModel { typedef typename Model<typename ReversedTypelist<Variables, NullType>::Result, NullType, Constraints, NullType>::Result Result; }; |
Now, the plan of action. For the exclusive or example, we start with something like this:
1 2 3 4 5 6 7 8 9 |
FullModel( [fixed variable 0 with value true; fixed variable 1 with value false; free variable 2], [ Constraint(!0, 1, 2), Constraint(0, !1, 2), Constraint(0, 1, !2), Constraint(!0, !1, !2) ] ) |
First, we add two more empty parameters. We also reverse the list of variables because we’ll reverse it later when evaluating:
1 2 3 4 5 6 7 8 9 10 11 |
Model( [free variable 2, fixed variable 1 with value false, fixed variable 0 with value true], [], // valuated variables [ Constraint(!0, 1, 2), Constraint(0, !1, 2), Constraint(0, 1, !2), Constraint(!0, !1, !2) ], [] // bound constraints ) |
Now, we want to iterate over variables until the list is empty. For each iteration, we want to use disjunction to split into two subtrees:
1 2 3 4 5 6 7 |
template<typename Head, typename Tail, typename BoundVariables, typename Constraints, typename BoundConstraints> struct Model<Typelist<Head, Tail>, BoundVariables, Constraints, BoundConstraints> { typedef typename Disjunction< typename Model<Tail, Typelist<typename Head::value1, BoundVariables>, Constraints, BoundConstraints>::Result, typename Model<Tail, Typelist<typename Head::value2, BoundVariables>, Constraints, BoundConstraints>::Result >::Result Result; }; |
So, the very first step of evaluation will take first variable and generate this first subtree:
1 2 3 4 5 6 7 8 9 10 11 |
Model( [fixed variable 1 with value false, fixed variable 0 with value true], [bound variable 2 with value false], // valuated variables [ Constraint(!0, 1, 2), Constraint(0, !1, 2), Constraint(0, 1, !2), Constraint(!0, !1, !2) ], [] // bound constraints ) |
You can see that we removed one unbound variable (from the first list) and added one bound variable (to the second list). We also need to do the same for the other possible value of the variable, so we generate another subtree:
1 2 3 4 5 6 7 8 9 10 11 |
Model( [fixed variable 1 with value false, fixed variable 0 with value true], [bound variable 2 with value true], // valuated variables [ Constraint(!0, 1, 2), Constraint(0, !1, 2), Constraint(0, 1, !2), Constraint(!0, !1, !2) ], [] // bound constraints ) |
Next steps are similar, but both generated subtrees will be the same (as variables are fixed). Ultimately, we end up with something like this:
1 2 3 4 5 6 7 8 9 10 11 |
Model( [], [variable 0 with value true, variable 1 with value false, variable 2 with value false], // valuated variables [ Constraint(!0, 1, 2), Constraint(0, !1, 2), Constraint(0, 1, !2), Constraint(!0, !1, !2) ], [] // bound constraints ) |
We bound all the variables. Now, we need to do the same for constraints:
1 2 3 4 |
template<typename BoundVariables, typename Head, typename Tail, typename BoundConstraints> struct Model<NullType, BoundVariables, Typelist<Head, Tail>, BoundConstraints> { typedef typename Model<NullType, BoundVariables, Tail, Typelist<BoundConstraint<Head, BoundVariables>, BoundConstraints> >::Result Result; }; |
In the first step, we take the first constraint and bound it with the variables to get something like this:
1 2 3 4 5 6 7 8 9 10 11 12 |
Model( [], [bound variable 0 with value true, bound variable 1 with value false, bound variable 2 with value false], // valuated variables [ Constraint(0, !1, 2), Constraint(0, 1, !2), Constraint(!0, !1, !2) ], [ BoundConstraint(!0, 1, 2) with bound variables(true, false, false), ] // bound constraints ) |
We do the same for all the constraints, and finally end up with this:
1 2 3 4 5 6 7 8 9 10 11 |
Model( [], [bound variable 0 with value true, bound variable 1 with value false, bound variable 2 with value false], // valuated variables [], [ BoundConstraint(0, !1, 2) with bound variables(true, false, false), BoundConstraint(0, 1, !2) with bound variables(true, false, false), BoundConstraint(!0, !1, !2) with bound variables(true, false, false), BoundConstraint(!0, 1, 2) with bound variables(true, false, false), ] // bound constraints ) |
We repeat a lot, but that’s not a problem.
Finally, we can start evaluating the constraints:
1 2 3 4 5 6 7 |
template<typename BoundVariables, typename Head, typename Tail> struct Model<NullType, BoundVariables, NullType, Typelist<Head, Tail> > { typedef typename Conjunction< typename Head::value, typename Model<NullType, BoundVariables, NullType, Tail>::Result >::Result Result; }; |
We unwrap the first constraint and turn it into a conjunction. Effectively, we’ll build a structure of this:
1 |
Conjunction(BoundConstraint(0, !1, 2) with bound variables(true, false, false), Conjunction(BoundConstraint(0, 1, !2) with bound variables(true, false, false), Conjunction(BoundConstraint(!0, !1, !2) with bound variables(true, false, false), Conjunction(BoundConstraint(!0, 1, 2) with bound variables(true, false, false), Model([], [bound variable 0 with value true, bound variable 1 with value false, bound variable 2 with value false], [], []))))) |
And now we can calculate the conjunctions. If the second parameter is empty model, then we simply return bound variables:
1 2 3 4 |
template<typename BoundVariables> struct Model<NullType, BoundVariables, NullType, NullType>{ typedef BoundVariables Result; }; |
Now, we need to refer back to the definition of conjunction we already saw:
1 2 |
template<typename BoundVariables> struct Conjunction<LiteralFalse, BoundVariables> { typedef NullType Result; }; template<typename BoundVariables> struct Conjunction<LiteralTrue, BoundVariables> { typedef BoundVariables Result; }; |
You can see that we’ll get some variables as a second parameter, and we’ll need to evaluate the first parameter which is BoundConstraint
. We already saw how it’s done:
1 2 3 4 5 6 7 8 |
template<int A, int B, int C, typename BoundVariables> struct BoundConstraint<Constraint<Negated<Literal<A> >, Id<Literal<B> >, Id<Literal<C> > >, BoundVariables> { typedef typename ThreeClause< typename Negated<typename TypeAt<BoundVariables, A>::Result>::Result, typename Id<typename TypeAt<BoundVariables, B>::Result>::Result, typename Id<typename TypeAt<BoundVariables, C>::Result>::Result >::value value; }; |
And here is where the magic happens. We extract variables from the list, we negate them accordingly, and then use the 3CNF logic:
1 |
template<> struct ThreeClause<LiteralFalse, LiteralFalse, LiteralFalse> { typedef LiteralFalse value; }; |
And we can see this constraint isn’t met. We’ll therefore return LiteralFalse
back to Conjunction
above and this will in turn return NullType
up the tree. You can trace the recursive calls further to figure out what happens next.
Printing
Last thing is printing. It’s rather straighforward:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
template<typename T> struct Print {}; template<typename Head, typename Tail> struct Print<Typelist<Head, Tail> >{ void print(){ std::cout<<Head::value<<std::endl; Print<Tail>().print(); } }; template<> struct Print<NullType>{ void print(){ std::cout<<"NullType"<<std::endl; } }; |
This simply prints the structure. The solution to SAT is calculated in the compilation time. Printing happens in runtime, so you need to run the application to get the precalculated result.
Summary
We already saw how to reduce ILP to 0-1 ILP, then to 3CNF SAT, and now we can calculate that using C++ templates. Therefore, we can solve our ILP problems using C++ compiler.