Another code-generated lpsolve-formatted IP example

Here is an another example of code that generates an lpsolve-formatted IP.

Here we wish to solve the problem of finding a pair of dice (i.e., finding what their face values should be) such that when thrown, the sum of the two dice is always a prime number.

Here is the Python (2) code.



#
# Matthew M. Conroy 2018
# 
# This code outputs an lpsolve-formatted LP which
# determines the faces of a pair of dice that yields
# only prime sums.
# 
# The faces are all distinct (i.e., no face value is repeated).
#

# this function determines if arg1 is prime (this is not
# a cleverly written method, but arg1 is very small, so it is fine)
def isprime(arg1): # we assume arg1 is a positive integer
	prime=1;
	for i in range(2,arg1-1):
		if ((arg1 % i)==0):
			prime=0
	return(prime);

sides = 3; 	# set the number of sides the dice will have
n = 12; 	# this is the upper bound for the face values; 
			# The large n is set, the slower the calculation, so it should
			# be determined experimentally, starting small and increasing
			# until solutions are found

# print the objective function
# Here the objective is to minimize the sum of all die faces
# so that the solution is in some sense "small".
outstring="min: ";
# a variables for the even faces
for i in range(2,n,2):
		outstring = outstring+"+"+str(i)+"*a_"+str(i)
# b variables for the odd faces
for i in range(1,n,2):
		outstring = outstring+"+"+str(i)+"*b_"+str(i)
print outstring+";"

# set the number of sides
# Here we use the fact that one die must have all even sides and the other must have all odd sides,
# since otherwise the sums would not (always) be prime.
outstring = "a_2"
for i in range(4,n,2):
	outstring = outstring +"+a_"+str(i)
outstring = outstring+"="+str(sides)+";"
print outstring
outstring = "b_1"
for i in range(3,n,2):
	outstring = outstring +"+b_"+str(i)
outstring = outstring+"="+str(sides)+";"
print outstring


# avoid composite sums
# We add a constraint for every pair of face values that would sum to a non-prime,
# forcing the avoidance of these pairs
outstring=""
for i in range(2,n,2):
	for j in range(1,n,2):
		if (not(isprime(i+j))):
			outstring="a_"+str(i)+"+b_"+str(j)+"<=1;"
			print outstring


# define variables to be bianary
outstring="bin "
for i in range(2,n,2):
	if (i==2):
		outstring = outstring +"a_"+str(i)
	else:
		outstring = outstring+",a_"+str(i)
for i in range(1,n,2):
	outstring = outstring +",b_"+str(i)
outstring = outstring+";"
print outstring


Suppose our two dice as "A" and "B".

The idea here is to introduce two sets of binary variables, ai and bi, with a=1 if and only if die "A" has a face value i, and bi=1 if and only if die "B" has a face value i.

We suppose that the maximum face will be less than n.

If we say the dice will have m sides, then we require a1+a2+...+an=m and b1+b2+...+bn=m.

Since we want only prime sums, we need to exclude the possibility that a face from A and a face from B could add to a non-prime. This achieved with a set of constraints in the form ai+bj ≤ 1 for all pairs (i,j) such that i+j is not prime, with i<n and j<n.

For large n and m, this will be a large number of constraints.

Finally, we need an objective function, so we choose to minimize the sum of all face values, so that the values are in a certain sense "small".

Running the code above (to find dice with three sides, and maximum face less than 12), we get this output:


min: +2*a_2+4*a_4+6*a_6+8*a_8+10*a_10+1*b_1+3*b_3+5*b_5+7*b_7+9*b_9+11*b_11;
a_2+a_4+a_6+a_8+a_10=3;
b_1+b_3+b_5+b_7+b_9+b_11=3;
a_2+b_7<=1;
a_4+b_5<=1;
a_4+b_11<=1;
a_6+b_3<=1;
a_6+b_9<=1;
a_8+b_1<=1;
a_8+b_7<=1;
a_10+b_5<=1;
a_10+b_11<=1;
bin a_2,a_4,a_6,a_8,a_10,b_1,b_3,b_5,b_7,b_9,b_11;

Saving this to a file, say, dice.lps, we can run lp_solve with the command (the -ia flag suppressed output of zero variables, making things easier to read)


lp_solve -ia dice.lps

and get the output


Value of objective function: 29.00000000

Actual values of the variables:
a_2                             1
a_4                             1
a_10                            1
b_1                             1
b_3                             1
b_9                             1

which shows that our dice should have sides {2,4,10} and {1,3,9}. You can quickly check that any value in the first set added to any value in the second set yields a prime number.

Matthew Conroy, University of Washington