class mangler: def __init__(self, n, r, na, ne, pl, nrr, nisr, out): self.n = n self.r = r self.graph = GNR(n, r) self.na = na self.ne = ne self.pl = pl self.nrr = nrr self.nisr = nisr self.out = out # Start down to the induced subgraph self.strip() # Generate the CNF formula r = reducer() print >> sys.stderr, 'Creating the CNF formula' numVars, cnf = r.reduce(self.graph.getGraph()) # Assign values and propagate print >> sys.stderr, 'Assigning the random variables' self.assignValues(numVars, cnf) def strip(self): for i in range(self.nrr): node = random.randint(0, self.n) self.graph.dropNode(node) self.n = self.n - 1 for i in range(self.nisr): self.graph.removeIndependentSet() def assignValues(self, numVars, cnf): # list of random variables to remove varsToAssign = [] for i in range(self.na): var = random.randint(0, numVars) if (var not in varsToAssign): varsToAssign.append(var) else: while (var in varsToAssign): var = random.randint(0, numVars) varsToAssign.append(var) # list of all possible variable assignments, start with all false assign = {} for i in range(self.na): assign[varsToAssign[i]] = False print("Assigning variables...") configIndex = 0 for config in range(2 ** self.na): print("Config " + str(configIndex)) newCnf = [] for c in cnf: # for each clause include = True clause = c[:] # Copy over the clause for l in c: # for each literal if include: index = -1 inVars = False if (l < 0 and (l * -1) in varsToAssign): index = (l * -1) inVars = True elif (l > 1 and l in varsToAssign): index = l inVars = True if inVars: if (assign[index] == True): # the literal is true, so drop the clauses include = False else: clause.remove(l) # remove the literal, it evaluated to false... if include: newCnf.append(clause) # Pass over the new CNF and find all unit clauses # This is for unit propagation... units = {} for c in newCnf: if (len(c) == 1): literal = c[0] if (literal < 0): units[literal * -1] = False # unitAssigns.append(False) # must be false else: units[literal] = True # unitAssigns.append(True) # Now that we have all of the unit literals with their truth values, perform the walk again... finalCnf = [] # compute average time for each of the results # multiply by 2^20 for hardness! # TODO: Make this into a method? (oldCnf, variable list with assignments, ...) sizeTwoClauses = 0 for c in newCnf: # for each clause include = True clause = c[:] # Copy over the clause for l in c: # for each literal if include: index = -1 inVars = False if (l < 0 and (l * -1) in units.keys()): index = (l * -1) inVars = True elif (l > 1 and l in units.keys()): index = l inVars = True if inVars: if (units[index] == True): # the literal is true, so drop the clauses include = False else: clause.remove(l) # remove the literal, it evaluated to false... if include and len(clause) > 0: # do not append empty clauses, they cause immediate unsatisfiability... if (len(clause) == 2): sizeTwoClauses = sizeTwoClauses + 1 finalCnf.append(clause) elif (len(clause) == 0): print("Found unsatisfiable clause in variable configuration " + str(configIndex) + ", discarding formula.") # Write the output file self.write(configIndex, numVars, finalCnf, sizeTwoClauses) configIndex = configIndex + 1 # advance the configuration for k in assign.keys(): if (assign[k] == True): # propagate to the next clause! assign[k] = False else: assign[k] = True break # hop out early... def write(self, index, numVars, cnf, numTwoClauses): print("Writing: " + str(self.out + "_" + str(index))) f = open(self.out + "_" + str(index), 'wb') header, clauses = makeDimacsCNF(numVars, cnf) f.write('c ' + str(numTwoClauses) + "\n") f.write(header + "\n") for c in clauses: for l in c: f.write(str(l) + " ") f.write("0 \n")
class injector: def __init__(self, n, r, sample, fix, na, smart, saturate, ne, nrr, nerr, nisr, out, dump): self.n = n self.r = r self.sample = sample self.graph = GNR(n, r) self.fix = fix self.na = na self.smart = smart self.saturate = saturate self.ne = ne self.nrr = nrr self.nerr = nerr self.nisr = nisr self.out = out self.dump = dump # Preliminary error checking if self.graph.getGraph().edges() < self.na: raise Exception("Cannot remove more edges than the graph contains.") if n < (nrr + nisr): raise Exception("Cannot remove more vertices than the graph contains.") # Generate the CNF formula # r = reducer() # print >> sys.stderr, 'Creating the original CNF formula' # numVars, cnf = r.reduce(self.graph.getGraph()) # self.write("reduced", numVars, cnf, 0) # Strip down to the induced subgraph # using the structural/random properties, as specified # by the command cmd line arguments print >>sys.stderr, "Stripping the graph" self.strip() print >>sys.stderr, "Filling out edges" # edgesAdded = self.fill() # Let exceptions carry up to -main- # Saturate by default! if saturate: print >>sys.stderr, "Saturating with edges..." self.graph.saturateAvoidK4() r = reducer() print >>sys.stderr, "Reducing to 3-SAT" numVars, cnf = r.reduce(self.graph.getGraph()) self.write("reduced", numVars, cnf, 0) if self.dump: print >>sys.stderr, "Dumping the modified graph" self.dumpGraph() # Assign values and propagate, if fix was set to true print >>sys.stderr, "Assigning the random variables" if self.fix: self.assignAndWrite(numVars, cnf) def dumpGraph(self): for e in self.graph.getGraph().edges(): print(str(e[0]) + " " + str(e[1])) def strip(self): for i in range(self.nrr): node = random.randint(0, self.n) self.graph.dropNode(node) self.n = self.n - 1 for i in range(self.nisr): self.graph.removeIndependentSet() for i in range(self.nerr): edge = random.randint(0, len(self.graph.getGraph().edges())) self.graph.dropEdge(edge) def fill(self): edgesAdded = 0 for i in range(self.ne): if self.graph.addRandomEdgeAvoidK4(): edgesAdded = edgesAdded + 1 return edgesAdded def edgesIntersect(self, e, vars, cnf): for c in cnf: for v in vars: if (v in c) and (e in c): return True return False def assignAndWrite(self, numVars, cnf): numClauses = len(cnf) numFormulas = 0 minTwoClauses = numClauses totalTwoClauses = 0 maxTwoClauses = 0 minThreeClauses = numClauses maxThreeClauses = 0 totalThreeClauses = 0 totalRatio = 0 # list of random variables to remove varsToAssign = [] if self.smart: loop = 0 THRESHOlD = 100 # There has to be a way to determine the maximum number of disjoint things... for i in range(self.na): if loop > THRESHOlD: break else: var = random.randint(0, numVars) loop = 0 while self.edgesIntersect(var, varsToAssign, cnf) and loop < THRESHOlD: loop = loop + 1 var = random.randint(0, numVars) if loop < THRESHOlD: varsToAssign.append(var) else: for i in range(self.na): var = random.randint(0, numVars) if var not in varsToAssign: varsToAssign.append(var) else: while var in varsToAssign: var = random.randint(0, numVars) varsToAssign.append(var) print >>sys.stderr, "Variables to assign: " + str(varsToAssign) # list of all possible variable assignments, start with all false assign = {} for i in range(self.na): assign[varsToAssign[i]] = False print >>sys.stderr, "Assigning variables..." configSamples = [] configIndex = 0 # for config in range(2 ** self.na): numSamples = 2 ** self.na while configIndex < self.sample or numFormulas == 0: # Select a random configuration not already in the set c = random.randint(0, numSamples) while c in configSamples: c = random.randint(0, numSamples) configSamples.append(c) # Advance the configuration array... for i in range(len(assign.keys())): if ((1 << i) & c) > 0: assign[assign.keys()[i]] = True else: assign[assign.keys()[i]] = False # Print out the configuration we're on... if configIndex % 500 == 0: print >>sys.stderr, "Config " + str(configIndex) newCnf = [] for c in cnf: # for each clause include = True clause = c[:] # Copy over the clause settled = False # print("Examining clause: " + str(clause)) for l in c: # for each literal if include and settled == False: index = -1 inVars = False negative = False if l < 0 and (l * -1) in varsToAssign: index = l * -1 inVars = True negative = True elif l > 1 and l in varsToAssign: index = l inVars = True if inVars: settled = True if assign[index] == True and negative == False: # the literal is true, so drop the clause # print("Dropping clause because " + str(index) + " was assigned to " + str(assign[index])) include = False elif assign[index] == True and negative == True: # print("Dropping literal " + str(index) + " from the clause because it was assigned to " + str(assign[index])) include = True clause.remove(l) # remove the literal, it evaluated to false... elif assign[index] == False and negative == False: # print("Dropping literal " + str(index) + " from the clause because it was assigned to " + str(assign[index])) include = True clause.remove(l) # remove the literal, it evaluated to false... elif assign[index] == False and negative == True: # print("Dropping clause because " + str(index) + " was assigned to " + str(assign[index])) include = False if include: # print("Resulting clause inserted: " + str(clause)) newCnf.append(clause) # Pass over the new CNF and find all unit clauses # This is for unit propagation... units = {} for c in newCnf: if len(c) == 1: literal = c[0] if literal < 0: units[literal * -1] = False else: units[literal] = True # Now that we have all of the unit literals with their truth values, perform the walk again... finalCnf = [] sizeTwoClauses = 0 sizeThreeClauses = 0 write = True for c in newCnf: # for each clause include = True clause = c[:] # Copy over the clause for l in c: # for each literal if include: index = -1 inVars = False if l < 0 and (l * -1) in units.keys(): index = l * -1 inVars = True elif l > 0 and l in units.keys(): index = l inVars = True if inVars: if units[index] == True: # the literal is true, so drop the clauses include = False print >>sys.stderr, "HOW CAN THIS HAPPEN?" return else: clause.remove(l) # remove the literal, it evaluated to false... if include and len(clause) > 0: # do not append empty clauses, they cause immediate unsatisfiability... if len(clause) == 2: sizeTwoClauses = sizeTwoClauses + 1 elif len(clause) == 3: sizeThreeClauses = sizeThreeClauses + 1 finalCnf.append(clause) elif len(clause) == 0: # print >> sys.stderr, "Found unsatisfiable clause in variable configuration " + str(configIndex) + ", discarding formula." write = False break configIndex = configIndex + 1 # Write the output file if write: # Compound total number of formulas numFormulas = numFormulas + 1 self.write(configIndex, numVars, finalCnf, sizeTwoClauses) # Compound the statistics for the two clauses if sizeTwoClauses < minTwoClauses: minTwoClauses = sizeTwoClauses if sizeTwoClauses > maxTwoClauses: maxTwoClauses = sizeTwoClauses totalTwoClauses = totalTwoClauses + sizeTwoClauses # Compound stats for three clauses if sizeThreeClauses < minThreeClauses: minThreeClauses = sizeThreeClauses if sizeThreeClauses > maxThreeClauses: maxThreeClauses = sizeThreeClauses totalThreeClauses = totalThreeClauses + sizeThreeClauses # Compound ratio totalRatio = totalRatio + (float(totalTwoClauses) / float(totalThreeClauses)) # Output the stats (if we actually wrote anything...) if numFormulas > 0: print >>sys.stderr, "Total number of formulas: " + str(numFormulas) print >>sys.stderr, "Minimum number of 2-clauses: " + str(minTwoClauses) print >>sys.stderr, "Maximum number of 2-clauses: " + str(maxTwoClauses) print >>sys.stderr, "Average number of 2-clauses: " + str(totalTwoClauses / numFormulas) print >>sys.stderr, "Minimum number of 3-clauses: " + str(minThreeClauses) print >>sys.stderr, "Maximum number of 3-clauses: " + str(maxThreeClauses) print >>sys.stderr, "Average number of 3-clauses: " + str(totalThreeClauses / numFormulas) print >>sys.stderr, "Average ratio of 2-to-3 clauses: " + str(totalRatio / numFormulas) def write(self, index, numVars, cnf, numTwoClauses): filename = self.out + "_" + str(index) + ".pickle" self.graph.dump(filename) print >>sys.stderr, str(self.out + "_" + str(index)) f = open(self.out + "_" + str(index), "wb") header, clauses = makeDimacsCNF(numVars, cnf) f.write("c " + str(numTwoClauses) + "\n") f.write(header + "\n") for c in clauses: for l in c: f.write(str(l) + " ") f.write("0 \n")
class injector: def __init__(self, n = 127, r = 3, fix = True, sample_size = 1, na = 0, smart = False, saturate = False, ne = 0, nrr = 0, nerr = 0, nisr = 0, out = ""): self.n = n self.r = r self.sample = sample_size self.graph = GNR(n, r) self.fix = fix self.na = na self.smart = smart self.saturate = saturate self.ne = ne self.nrr = nrr self.nerr = nerr self.nisr = nisr self.out = out def apply_modifications(self, pcmap = None): # Preliminary error checking if (self.graph.getGraph().edges() < self.na): raise Exception("Cannot remove more edges than the graph contains.") if (self.n < (self.nrr + self.nisr)): raise Exception("Cannot remove more vertices than the graph contains.") # Strip down to the induced subgraph # using the structural/random properties, as specified # by the command cmd line arguments print >> sys.stderr, 'Stripping the graph...' print >> sys.stderr, "Random vertices to remove: " + str(self.nrr) print >> sys.stderr, "Random edges to remove: " + str(self.nerr) print >> sys.stderr, "Maximal independent sets to remove: " + str(self.nisr) self.strip() print >> sys.stderr, 'Done.' # Saturate by default! if (self.saturate): print >> sys.stderr, "Saturating with edges..." self.graph.saturateAvoidK4(); print >> sys.stderr, "Done." # Now reduce the graph to SAT r = reducer() print >> sys.stderr, "Reducing to 3-SAT..." numVars, edgeMap, cnf = r.reduce(self.graph.getGraph()) print >> sys.stderr, "Done." # Output or not... print >> sys.stderr, "Writing the original CNF formula..." self.write("reduce", numVars, cnf, 0) print >> sys.stderr, "Done." print >> sys.stderr, "Writing reduced graph edge list..." self.dumpGraph() print >> sys.stderr, "Done." return numVars, edgeMap, cnf def inject(self): # Apply graph modifications numVars, edgeMap, cnf = self.apply_modifications() # Assign values and propagate, if fix was set to true print >> sys.stderr, "Performing edge assignment and writing the output..." self.assignAndWrite(numVars, edgeMap, cnf) print >> sys.stderr, "Done." def inject_arbitrary_graph_with_edge_precoloring(self, gnrG, pcmap): ''' For an arbitrary GNR graph G and set of precolored edges, precolor the edges and perform the reduction to SAT using the same approach as the general inject() above. If the structure of the graph is to be modified, then the appropriate parameters should be passed into the injector constructor above. ''' # Apply graph modifications self.n = gnrG.n self.r = gnrG.r self.graph = gnrG numVars, edgeMap, cnf = self.apply_modifications() print >> sys.stderr, "pcmap: " + str(pcmap) # Assign values and propagate, if fix was set to true print >> sys.stderr, "Performing edge assignment and writing the output..." self.assignAndWrite(numVars, edgeMap, cnf, pcmap) print >> sys.stderr, "Done." # Write the edge-list representation of the graph (after preprocessed) # as well as the pickled graph object def dumpGraph(self): filename = self.out + "_graph.pickle" self.graph.dump(filename) # Strip down the graph according to the specified parameters def strip(self): for i in range(self.nrr): node = random.randint(0, self.n) self.graph.dropNode(node) self.n = self.n - 1 for i in range(self.nisr): self.graph.removeIndependentSet() for i in range(self.nerr): edge = random.randint(0, len(self.graph.getGraph().edges())) self.graph.dropEdge(edge) # Fill in the graph with the parameters specificed def fill(self): edgesAdded = 0 for i in range(self.ne): if self.graph.addRandomEdgeAvoidK4(): edgesAdded = edgesAdded + 1 return edgesAdded def edgesIntersect(self, e, vars, cnf): for c in cnf: for v in vars: if (v in c) and (e in c): return True return False def vte(self, var, edgeMap): for e in self.graph.edges(): if edgeMap[e] == var: return e def pickRandomEdgeToAssign(varsToAssign, numVars, edgeMap, pcmap): var = random.randint(0, numVars) if (pcmap == None): if (var in varsToAssign): while (var in varsToAssign): var = random.randint(0, numVars) else: ei = self.vte(var, edgeMap) if (var in varsToAssign or edgeMap[ei] in pcmap[0] or edgeMap[ei] in pcmap[1]): while (var in varsToAssign or edgeMap[ei] in pcmap[0] or edgeMap[ei] in pcmap[1]): var = random.randint(0, numVars) ei = self.vte(var, edgeMap) return var def assignAndWrite(self, numVars, edgeMap, cnf, pcmap = None): numClauses = len(cnf) numFormulas = 0 minTwoClauses = numClauses totalTwoClauses = 0 maxTwoClauses = 0 minThreeClauses = numClauses maxThreeClauses = 0 totalThreeClauses = 0 totalRatio = 0 # Pick the set of edges to assign # Smart selection of edges picks those that are disjoint (well, it tries to THRESHOLD times), # and then it gives up and just picks a random one # "Dumb" selection just picks random edges, not caring whether or not they are disjoint varsToAssign = [] if (self.smart): loop = 0 THRESHOLD = 100 # There has to be a way to determine the maximum number of disjoint things... for i in range(self.na): if loop > THRESHOLD: break else: var = random.randint(0, numVars) loop = 0 ei = self.vte(var, edgeMap) while (self.edgesIntersect(var, varsToAssign, cnf) and loop < THRESHOLD or edgeMap[ei] in pcmap[0] or edgeMap[ei] in pcmap[1]): loop = loop + 1 var = random.randint(0, numVars) if (loop < THRESHOLD): # We found a disjoint one... varsToAssign.append(var) else: # Failed to find a disjoint one, so pick something at random varsToAssign.append(pickRandomEdgeToAssign(varsToAssign, numVars, edgeMap, pcmap)) else: for i in range(self.na): varsToAssign.append(pickRandomEdgeToAssign(varsToAssign, numVars, edgeMap, pcmap)) # debug print >> sys.stderr, "Variables to assign: " + str(varsToAssign) # list of all possible variable assignments, start with all false assign = {} for i in range(self.na): assign[varsToAssign[i]] = False print >> sys.stderr, "Assigning variables..." configSamples = [] configIndex = 0 #for config in range(2 ** self.na): numSamples = 2 ** self.na while (configIndex < self.sample or numFormulas == 0): # Select a random configuration not already in the set c = random.randint(0, numSamples) while (c in configSamples): c = random.randint(0, numSamples) configSamples.append(c) # Advance the configuration array... for i in range(len(assign.keys())): if (((1 << i) & c) > 0): assign[assign.keys()[i]] = True else: assign[assign.keys()[i]] = False # Print out the configuration we're on... if (configIndex % 500 == 0): print >> sys.stderr, "Config " + str(configIndex) newCnf = [] # Quick hack so we don't throw NPEs if pcmap == None: print >> sys.stderr, "nullifying the pcmap because one was not provided" pcmap = {} pcmap[0] = [] pcmap[1] = [] else: print >> sys.stdout, str(pcmap) for c in cnf: # for each clause include = True clause = c[:] # Copy over the clause settled = False # print("Examining clause: " + str(clause)) for l in c: # for each literal if include and settled == False: index = -1 inVars = False negative = False ei = (0,0) if (l < 0): ei = self.vte(l * -1, edgeMap) else: ei = self.vte(l, edgeMap) if (l < 0 and ((l * -1) in varsToAssign or ei in pcmap[0] or ei in pcmap[1])): index = (l * -1) inVars = True negative = True elif (l > 1 and (l in varsToAssign or ei in pcmap[0] or ei in pcmap[1])): index = l inVars = True # Now check to see if this is a variable we're precoloring, and if so, handle accordingly if inVars: settled = True truthValue = False if ei in pcmap[0]: truthValue = False elif ei in pcmap[1]: truthValue = True else: truthValue = assign[index] # Use the truth value assignment and value of the literal (whether it was negated or not) to determine what value to give it if (truthValue == True and negative == False): # the literal is true, so drop the clause include = False elif (truthValue == True and negative == True): include = True clause.remove(l) # remove the literal, it evaluated to false... elif (truthValue == False and negative == False): include = True clause.remove(l) # remove the literal, it evaluated to false... elif (truthValue == False and negative == True): include = False if include: # print("Resulting clause inserted: " + str(clause)) newCnf.append(clause) # Pass over the new CNF and find all unit clauses # This is for unit propagation... units = {} for c in newCnf: if (len(c) == 1): literal = c[0] if (literal < 0): units[literal * -1] = False else: units[literal] = True # Now that we have all of the unit literals with their truth values, perform the walk again and do the substitution # where we either remove clauses that evaluate to true or discard variables from clauses if the variable is false finalCnf = [] sizeTwoClauses = 0 sizeThreeClauses = 0 write = True for c in newCnf: # for each clause include = True clause = c[:] # Copy over the clause for l in c: # for each literal if include: index = -1 inVars = False if (l < 0 and (l * -1) in units.keys()): index = (l * -1) inVars = True elif (l > 0 and l in units.keys()): index = l inVars = True if inVars: if (units[index] == True): # the literal is true, so drop the clause include = False else: # the literal is false, so remove the variable from the clause clause.remove(l) # remove the literal, it evaluated to false... # Do not append empty clauses, they cause immediate unsatisfiability... if include and len(clause) > 0: if (len(clause) == 2): sizeTwoClauses = sizeTwoClauses + 1 elif (len(clause) == 3): sizeThreeClauses = sizeThreeClauses + 1 finalCnf.append(clause) elif (len(clause) == 0): # if the clause is empty, then all literals evaluted to false, and so the # solution is unsatisfiable, and we don't write the output... write = False break # Bump up our unique identifier configIndex = configIndex + 1 # Write the output file if (write): # Compound total number of formulas collected and written so far numFormulas = numFormulas + 1 self.write(configIndex, numVars, finalCnf, sizeTwoClauses) # Compound the statistics for the two clauses if (sizeTwoClauses < minTwoClauses): minTwoClauses = sizeTwoClauses if (sizeTwoClauses > maxTwoClauses): maxTwoClauses = sizeTwoClauses totalTwoClauses = totalTwoClauses + sizeTwoClauses # Compound stats for three clauses if (sizeThreeClauses < minThreeClauses): minThreeClauses = sizeThreeClauses if (sizeThreeClauses > maxThreeClauses): maxThreeClauses = sizeThreeClauses totalThreeClauses = totalThreeClauses + sizeThreeClauses # Output the stats (if we actually wrote anything...) if (numFormulas > 0): # Compute the 2/3 ratio first totalRatio = 1.0 if totalThreeClauses != 0: totalRatio = totalRatio + (float(totalTwoClauses) / float(totalThreeClauses)) # Now display the stats print >> sys.stderr, "Total number of formulas: " + str(numFormulas) print >> sys.stderr, "Minimum number of 2-clauses: " + str(minTwoClauses) print >> sys.stderr, "Maximum number of 2-clauses: " + str(maxTwoClauses) print >> sys.stderr, "Average number of 2-clauses: " + str(totalTwoClauses / numFormulas) print >> sys.stderr, "Minimum number of 3-clauses: " + str(minThreeClauses) print >> sys.stderr, "Maximum number of 3-clauses: " + str(maxThreeClauses) print >> sys.stderr, "Average number of 3-clauses: " + str(totalThreeClauses / numFormulas) print >> sys.stderr, "Average ratio of 2-to-3 clauses: " + str(totalRatio / numFormulas) # Write the resulting CNF file to disk def write(self, index, numVars, cnf, numTwoClauses): print >> sys.stderr, str(self.out + "_" + str(index)) f = open(self.out + "_" + str(index), 'wb') header, clauses = makeDimacsCNF(numVars, cnf) f.write('c ' + str(numTwoClauses) + "\n") f.write(header + "\n") for c in clauses: for l in c: f.write(str(l) + " ") f.write("0 \n")