Google OR tools: how to evaluate complex or multi-level boolean constraints - or-tools

Set up
I am using the google OR tools as a constraint programming solver:
from ortools.sat.python import cp_model
I have defined the following BoolVars
model = cp_model.CpModel()
a = model.NewBoolVar("a")
b = model.NewBoolVar("b")
c = model.NewBoolVar("c")
d = model.NewBoolVar("d")
e = model.NewBoolVar("e")
f = model.NewBoolVar("f")
g = model.NewBoolVar("g")
Question
I need to add a complex boolean constraint to the model. Something like
(a || b) && (d || e) == g
How can I add a complex boolean constraint like this to the model?
PS: I couldn't find this information immediately online, but found a solution based on an answer I got on a related problem here and another related problem of another person here. I summarize my findings here in Q&A style in the hope that they will be useful for someone.

This kind of constraint cannot be added at once to the model. The constraint needs to be split up in its components (and & or gates). Each basic component needs to be set equal to a new BoolVar. These are then combined to add the final constraint.
Breakdown to basic components
We'll do this as follows:
(a || b) == c
(d || e) == f
(c && f) == g
This last equation is equivalent to the original equation:
(a || b) && (d || e) == g
Storing basic constraints in new BoolVars
An "OR" type of equation can be evaluated with the following function:
def evaluate_or(a, b, c):
# Either a or b or both must be 1 if c is 1.
model.AddBoolOr([a, b]).OnlyEnforceIf(c)
# The above line is not sufficient, as no constraints are defined if c==0 (see reference documentation of
# "OnlyEnforceIf". We therefore add another line to cover the case when c==0:
# a and b must both be 0 if c is 0
model.AddBoolAnd([a.Not(), b.Not()]).OnlyEnforceIf([c.Not()])
An "AND" type of equation can similarily be evaluated with the following function:
def evaluate_and(a, b, c):
# Both a and b must be 1 if c is 1
model.AddBoolAnd([a, b]).OnlyEnforceIf(c)
#What happens if c is 0? This is still undefined thus let us add another line:
# Either a or b or both must be 0 if c is 0.
model.AddBoolOr([a.Not(), b.Not()]).OnlyEnforceIf(c.Not())
In this specific case the basic constraints are twice an OR gate. We store them in c and f:
evaluate_or(a, b, c)
evaluate_or(d, e, f)
Adding the complex constraint
This is now really simple and can be done using the BoolVars from the previous step and an AND gate:
evaluate_and(c, f, g)
Discussion
Any complex constraint can be added using intermediary BoolVars and the defined OR and AND gate functions above. The constraint just needs to be broken down into its basic components.
Full program
This is the full program:
from ortools.sat.python import cp_model
model = cp_model.CpModel()
a = model.NewBoolVar("a")
b = model.NewBoolVar("b")
c = model.NewBoolVar("c")
d = model.NewBoolVar("d")
e = model.NewBoolVar("e")
f = model.NewBoolVar("f")
g = model.NewBoolVar("g")
def evaluate_or(a, b, c):
# Either a or b or both must be 1 if c is 1.
model.AddBoolOr([a, b]).OnlyEnforceIf(c)
# The above line is not sufficient, as no constraints are defined if c==0 (see reference documentation of
# "OnlyEnforceIf". We therefore add another line to cover the case when c==0:
# a and b must both be 0 if c is 0
model.AddBoolAnd([a.Not(), b.Not()]).OnlyEnforceIf([c.Not()])
def evaluate_and(a, b, c):
# Both a and b must be 1 if c is 1
model.AddBoolAnd([a, b]).OnlyEnforceIf(c)
#What happens if c is 0? This is still undefined thus let us add another line:
# Either a or b or both must be 0 if c is 0.
model.AddBoolOr([a.Not(), b.Not()]).OnlyEnforceIf(c.Not())
#Add the constraints
evaluate_or(a, b, c)
evaluate_or(d, e, f)
evaluate_and(c, f, g)
# Solve the model.
solver = cp_model.CpSolver()
solver.parameters.enumerate_all_solutions = True
status = solver.Solve(model, cp_model.VarArraySolutionPrinter([a, b, c, d, e, f, g]))
Output
The following output is obtained:
Solution 0, time = 0.00 s
a = 0 b = 0 c = 0 d = 1 e = 0 f = 1 g = 0
Solution 1, time = 0.00 s
a = 0 b = 0 c = 0 d = 1 e = 1 f = 1 g = 0
Solution 2, time = 0.00 s
a = 0 b = 0 c = 0 d = 0 e = 1 f = 1 g = 0
Solution 3, time = 0.00 s
a = 0 b = 0 c = 0 d = 0 e = 0 f = 0 g = 0
Solution 4, time = 0.00 s
a = 0 b = 1 c = 1 d = 0 e = 0 f = 0 g = 0
Solution 5, time = 0.00 s
a = 0 b = 1 c = 1 d = 1 e = 0 f = 1 g = 1
Solution 6, time = 0.00 s
a = 0 b = 1 c = 1 d = 1 e = 1 f = 1 g = 1
Solution 7, time = 0.00 s
a = 0 b = 1 c = 1 d = 0 e = 1 f = 1 g = 1
Solution 8, time = 0.00 s
a = 1 b = 1 c = 1 d = 0 e = 1 f = 1 g = 1
Solution 9, time = 0.00 s
a = 1 b = 1 c = 1 d = 1 e = 1 f = 1 g = 1
Solution 10, time = 0.00 s
a = 1 b = 1 c = 1 d = 1 e = 0 f = 1 g = 1
Solution 11, time = 0.00 s
a = 1 b = 1 c = 1 d = 0 e = 0 f = 0 g = 0
Solution 12, time = 0.00 s
a = 1 b = 0 c = 1 d = 0 e = 0 f = 0 g = 0
Solution 13, time = 0.00 s
a = 1 b = 0 c = 1 d = 0 e = 1 f = 1 g = 1
Solution 14, time = 0.00 s
a = 1 b = 0 c = 1 d = 1 e = 1 f = 1 g = 1
Solution 15, time = 0.00 s
a = 1 b = 0 c = 1 d = 1 e = 0 f = 1 g = 1

Related

Boolean Simplification - Q=A.B.(~B+C)+B.C+B

I've been struggling with boolean simplification in class, and took it to practice some more at home. I found a list of questions, but they don't have any answers or workings. This one I'm stuck on, if you could answer clearly showing each step I'd much appreciate:
Q=A.B.(~B+C)+B.C+B
I tried looking for a calculator to give me the answer and then to work out how to get to that, but I'm lost
(I'm new to this)
Edit: ~B = NOT B
I've never done this, so I'm using this site to help me.
A.B.(B' + C) = A.(B.B' + B.C) = A.(0 + B.C) = A.(B.C)
So the expression is now A.(B.C) + B.C + B.
Not sure about this, but I'm guessing A.(B.C) + (B.C) = (A + 1).(B.C).
This equals A.(B.C).
So the expression is now A.(B.C) + B.
As A.(B + C) = B.(A.C), the expression is now B.(A.C) + B, which equals (B + 1).(A.C) = B.(A.C).
NOTE: This isn't complete yet, so please avoid downvoting as I'm not finished yet (posted this to help the OP understand the first part).
Let's be lazy and use sympy, a Python library for symbolic computation.
>>> from sympy import *
>>> from sympy.logic import simplify_logic
>>> a, b, c = symbols('a, b, c')
>>> expr = a & b & (~b | c) | b & c | b # A.B.(~B+C)+B.C+B
>>> simplify_logic(expr)
b
There are two ways to go about such a formula:
Applying simplifications,
Brute force
Let's look at brute force first. The following is a dense truth table (for a better looking table, look at Wα), enumerating all possible value for a, b and c, alongside the values of the expression.
a b c -- a & b & (~b | c) | b & c | b = Q
0 0 0 0 0 10 1 0 0 0 0 0 = 0
0 0 1 0 0 10 1 1 0 0 1 0 = 0
0 1 0 0 1 01 0 0 1 0 0 1 = 1
0 1 1 0 1 01 1 1 1 1 1 1 = 1
1 0 0 1 0 10 1 0 0 0 0 0 = 0
1 0 1 1 0 10 1 1 0 0 1 0 = 0
1 1 0 1 1 01 1 0 1 0 0 1 = 1
1 1 1 1 1 01 1 1 1 1 1 1 = 1
You can also think of the expression as a tree, which will depend on the precedence rules (e.g. usually AND binds stronger than OR, see also this question on math.se).
So the expression:
a & b & (~b | c) | b & c | b
is a disjunction of three terms:
a & b & (~b | c)
b & c
b
You can try to reason about the individual terms, knowing that only one has to be true (as this is a disjunction).
The last two will be true, if and only if b is true. For the first, this a bit harder to see, but if you look closely: you have now a conjunction (terms concatenated by AND): All of them must be true, for the whole expression to be true, so a and b must be true. Especially b must be true.
In summary: For the whole expression to be true, in all three top-level cases, b must be true (and it will be false, if b is false). So it simplifies to just b.
Explore more on Wolfram Alpha:
https://www.wolframalpha.com/input/?i=a+%26+b+%26+(~b+%7C+c)+%7C+b+%26+c+%7C+b
A.B.(~B+C) + B.C + B = A.B.~B + A.B.C + B.C + B ; Distribution
= A.B.C + B.C + B ; Because B.~B = 0
= B.C + B ; Because A.B.C <= B.C
= B ; Because B.C <= B

MATLAB compute length of an integer

I'm trying to compute the length of an integer.
For example:
a = 1.1234; b = 33; c = 100; d = -222;
e = lengthint([a,b,c,d])
Expected output:
e = 1 2 3 3
I tried using this:
e = max(ceil(log10(abs([a,b,c,d]))),1)
but this is the output:
e = 1 2 2 3
So there is a problem with numbers that are multiples of 10.
You can do something like this -
A = [a,b,c,d]
lens = floor(log10(abs(A)))+1
lens(lens<0) = 0 %// Assuming that 0.xx numbers to have zero lengths
Sample runs:
Case #1:
>> A = [0.00001234, 1.1234, 33, 10, -222];
>> lens = floor(log10(abs(A)))+1;
>> lens(lens<0) = 0
lens =
0 1 2 2 3
Case #2:
>> A = [-0.00001234, 1.1234 33, 10, -222, 0];
>> lens = floor(log10(abs(A)))+1;
>> lens(lens<0) = 0
lens =
0 1 2 2 3 0
Another option would be to convert them to strings and check the length:
cellfun(#(x)length(num2str(abs(fix(x)))),{a,b,c,d});
the only complication is that you need cells to keep your strings separate.
Output from #Divakar's example input:
>> a1 = 0.00001234; a2 = 1.1234; b = 33; c = 100; d = -222;
>> cellfun(#(x)length(num2str(abs(fix(x)))),{a1,a2,b,c,d})
ans =
1 1 2 3 3
so it will obviously not give 0 for the 1e-5 case.

Efficient Multiplication of Matrices with Large Numbers of Zeroes

I have two arrays that take the following form:
0…0…0 0 0 0…0 0…0…0 0 0 0…0
⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮
0…0 0 0 0 0…0 0…0 0 0 0 0…0
A = 0…0 1 2 3 0…0 B = 0…0 9 8 7 0…0
0…0 4 5 6 0…0 0…0 6 5 4 0…0
0…0 0 0 0 0…0 0…0 0 0 0 0…0
⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮
0…0…0 0 0 0…0 0…0…0 0 0 0…0
The size of the non-zero areas of A and B may not be exactly the same, but the diagram above is already getting a bit unwieldy.
Ultimately, the value I'm after is sum(sum(A .* B)). I feel like there must be a way to only multiply the non-zero elements, but every approach I can come up with seems to cause MATLAB to make a copy of the matrix, which utterly destroys any gains made by reducing the number of operations. B is reused for several iterations of the inner loop, so I can amortize expensive calculations on B over many loop iterations.
I've tried the following approaches so far:
Naive Approach:
function C = innerLoop(A, B)
C = sum(sum(A .* B))
end
innerLoop takes about 4.3 seconds over 86,000 calls using this. (Based on MATLAB's "Run and Time" functionality.)
Shrinking B First:
function B = resize(self, B1)
rows = abs(sum(B, 2)) > 1e-4;
top = find(rows, 1, 'first');
bot = find(rows, 1, 'last');
cols = abs(sum(B, 1)) > 1e-4;
left = find(cols, 1, 'first');
right = find(cols, 1, 'last');
self.Rows = top:bot; % Store in class properties for use in inner loop
self.Cols = left:right; % Store in class properties for use in inner loop
B = B(top:bot, left:right);
end
function C = innerLoop(A, B)
result = A(self.Rows, self.Cols) .* B;
C = sum(sum(result));
end
My hope with this approach was that MATLAB would realize that I wasn't writing to A and elide the copy, but this approach spends about 6.8 seconds in innerLoop.
I also tried only calculation the offsets outside innerLoop in the hopes that MATLAB might be able to pick up on the fact that I'm using the same subscripts on both matrices to optimize things:
function B = resize(self, B1)
rows = abs(sum(B, 2)) > 1e-4;
top = find(rows, 1, 'first');
bot = find(rows, 1, 'last');
cols = abs(sum(B, 1)) > 1e-4;
left = find(cols, 1, 'first');
right = find(cols, 1, 'last');
self.Rows = top:bot; % Store in class properties for use in inner loop
self.Cols = left:right; % Store in class properties for use in inner loop
end
function C = innerLoop(A, B)
result = A(self.Rows, self.Cols) .* B(self.Rows, self.Cols);
C = sum(sum(result));
end
Unfortunately this was the slowest yet at about 8.6 seconds.
I also tried looping with the following code:
function C = innerLoop(A, B)
C = 0;
for i = self.Rows
for j = self.Cols
C = C + field(i, j) * self.Sensitivity.Z(i, j);
end
end
end
I know that looping used to be very slow in MATLAB, but I've read some papers indicating that it is much faster than it used to be. That said, if the loop version ever finishes running, I'll let you know how long it took, but it's well over a couple minutes by now.
Any suggestions on how to optimize this would be greatly appreciated!
You can use sparse matrices for this problem. Matlab handles different sizes of the «non-sparse-part» automatically. To get a sparse matrix, the sparse-function is used. After that you can do the element-wise multiplication and then sum all elements of C in a separate line.
A = [0 0 0 0 0 0 0;
0 0 0 0 0 0 0;
0 0 1 2 3 0 0;
0 0 4 5 6 0 0;
0 0 0 0 0 0 0;
0 0 0 0 0 0 0];
B = [0 0 0 0 0 0 0;
0 0 0 0 0 0 0;
0 0 9 8 7 0 0;
0 0 6 5 4 0 0;
0 0 0 0 0 0 0;
0 0 0 0 0 0 0];
A = sparse(A);
B = sparse(B);
C = A .* B;
sum(C(:))
This is a rewrite of my initial post
I stand corrected. I don't know what went wrong in my former test. I thought it may have been a 32 vs 64 bit implementation of the sparse algorithm but not even. After careful re-running of the benchmark on 2 different machines, the sparse method wins then all.
Benchmark code:
function ExecTimes = bench_sum_sparse
nOrder = (1:9).' * 10.^(2:3) ; nOrder = [nOrder(:) ; (1:2).'*1e4] ;
%// nOrder = (1:30)*100 ;
npt = numel(nOrder) ;
ExecTimes = zeros( npt , 3 ) ;
fprintf('\n%s%5d \n','Calculating for N = ',0) ;
for k = 1:npt
% // sample data
N = nOrder(k) ;
fprintf('\b\b\b\b\b\b%5d\n',N) ; % // display progress
A = zeros(N) ;
B = A ;
innerMat = (1:10).'*(1:10) ; %'
ixInnerMat = innerMat + N/2 - 5 ;
A(ixInnerMat) = innerMat ;
B(ixInnerMat) = innerMat ;
% // benchmark
f1 = #() innerLoop(A,B) ;
ExecTimes(k,1) = timeit( f1 ) ;
clear f1
f2 = #() sum_logicIndex(A, B) ;
ExecTimes(k,2) = timeit( f2 ) ;
clear f2
A = sparse(A);
B = sparse(B);
f3 = #() sum_sparse(A,B) ;
ExecTimes(k,3) = timeit( f3 ) ;
clear f3
%// checksum1 = f1() - f2 ()
%// checksum2 = f1() - f3 ()
end
end
function C = innerLoop(A, B)
C = sum(sum(A .* B)) ;
end
function C = sum_logicIndex(A,B)
idx = A>0 & B>0 ;
C = sum(sum(A(idx).*B(idx))) ;
end
function C = sum_sparse(A,B)
C = A .* B;
C = sum(C(:)) ;
end
All tests ran on Matlab 2013b
64 bit Machine : Intel I7-3820 # 3.6GHz - 16 GB RAM - Windows 7
32 bit Machine : Intel E2200 # 2.2GHz - 3GB RAM - Windows 8.1

Replace rows having at least one duplicate number or one zero by a user defined row

I have a matrix having rows with repeated numbers.
A= [ 2 3 6;
4 7 4;
8 7 2;
1 3 1;
7 8 2 ]
The codes below find those rows and replace them with a Dummy_row [1 2 3]
new_A=[ 2 3 6;
1 2 3;
8 7 2;
1 2 3;
7 8 2 ]
This are the codes:
CODE NUMBER 1 (#Bruno)
Dummy_row = [1 2 3];
b = any(~diff(sort(A,2),1,2),2);
A(b,:) = repmat(Dummy_row,sum(b),1)
CODE NUMBER 2 (#Kamtal)
Dummy_row = [1 2 3];
b = diff(sort(A,2),1,2);
b = sum(b == 0,2);
b = b > 0;
c = repmat(Dummy_row,sum(b),1);
b = b' .* (1:length(b));
b = b(b > 0);
newA = A;
newA(b,:) = c
Note: both codes Number 1 and 2 perform the task efficiently.
Question
How can this code(either code num 1 or num 2) be modified such that it also replaces any rows having at least one zero with the Dummy_row?
Code 1
b = any(~diff(sort(A,2),1,2),2) | any(A==0,2); % <-- Only change
A(b,:) = repmat(Dummy_row,sum(b),1);
Code 2
b = diff(sort(A,2),1,2);
b = sum(b == 0,2);
b = (b > 0) | any(A==0,2); % <-- Only change
c = repmat(Dummy_row,sum(b),1);
b = b' .* (1:length(b));
b = b(b > 0);
newA = A;
newA(b,:) = c;
By the way: Code1 basically does the same thing that Code2 does, just that it uses logical-indexing instead of doing the unnecessary conversion from logical indexes to index positions.

2D matrix of matrices

I have two diagonal matrices. I am trying to build a larger block diagonal matrix from them. For example if I had this:
D = diag(zeros(3,1)+1)
D =
1 0 0
0 1 0
0 0 1
and...
E = diag(zeros(2,1)+2, -1) + diag(zeros(2,1)+2, +1) + diag(zeros(3,1)+4)
E =
4 2 0
2 4 2
0 2 4
I have an equation that says A*U = X
Where A is
[E D 0
D E D
0 D E]
This is for 3x3. 5x5 would look like this:
A =
[E D 0 0 0
D E D 0 0
0 D E D 0
0 0 D E D
0 0 0 D E]
A would be another diagonal matrix consisting of these matrices. I need to produce a 40x40 and it would take a VERY LONG TIME to do manually, of course.
How can I define that? I haven't figured out how to use blkdiag to construct.
I solved this on my own manually because I could never find a Matlab function to help me.
for n = 1:Distance_Resolution
A(((n-1)*Distance_Resolution +1):n*Distance_Resolution, ((n-1)*Distance_Resolution +1):n*Distance_Resolution) = A1;
if n == Distance_Resolution
else
A((n*Distance_Resolution+1):(n+1)*(Distance_Resolution), ((n-1)*Distance_Resolution+1:n*Distance_Resolution)) = A2;
A((n-1)*Distance_Resolution+1:n*Distance_Resolution, (n*Distance_Resolution+1):(n+1)*(Distance_Resolution)) = A2;
end
end
This will produce a block matrix that has the above specified demands and is of length Distance_Resolution x Distance_Resolution x Distance_Resolution. I defined A1 and A2 through help from above poster (Fo is just a constant here):
vector = zeros(Distance_Resolution,1) - Fo;
A2 = diag(vector);
A1 = toeplitz([1+4*Fo, -Fo, zeros(1,Distance_Resolution-2)]);
This is a workable code snippet, but I am still looking for a smarter way to code it.